From b2d33b2674bf74eaffc23ede74d5d147b0d15fc8 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 9 Aug 2024 17:06:02 +0200 Subject: [PATCH 01/65] Set up project structure --- .gitignore | 24 + README.md | 33 +- docs/GUIDELINES.md | 43 + eslint.config.js | 38 + index.html | 13 + package-lock.json | 4648 +++++++++++++++++ package.json | 31 + public/vite.svg | 1 + src/app/App.jsx | 11 + src/app/pages/home/Home.css | 42 + src/app/pages/home/Home.jsx | 35 + src/app/pages/workspace/DataTable.jsx | 0 src/app/pages/workspace/Workspace.css | 0 src/app/pages/workspace/Workspace.jsx | 0 .../modification/ModSubcomponents.jsx | 0 .../workspace/modification/Modification.jsx | 0 src/assets/react.svg | 1 + src/components/Diagram.jsx | 0 src/entities/DataEntity.js | 0 src/entities/DiagramEntity.js | 0 src/entities/WorkspaceEntity.js | 0 src/exceptions/GlobalErrorHandler.js | 0 src/index.css | 68 + src/main.jsx | 10 + src/services/DataAnalysisService.js | 0 src/services/DataService.js | 0 src/services/DiagramService.js | 0 src/services/WorkspaceService.js | 0 vite.config.js | 7 + 29 files changed, 5004 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 docs/GUIDELINES.md create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/vite.svg create mode 100644 src/app/App.jsx create mode 100644 src/app/pages/home/Home.css create mode 100644 src/app/pages/home/Home.jsx create mode 100644 src/app/pages/workspace/DataTable.jsx create mode 100644 src/app/pages/workspace/Workspace.css create mode 100644 src/app/pages/workspace/Workspace.jsx create mode 100644 src/app/pages/workspace/modification/ModSubcomponents.jsx create mode 100644 src/app/pages/workspace/modification/Modification.jsx create mode 100644 src/assets/react.svg create mode 100644 src/components/Diagram.jsx create mode 100644 src/entities/DataEntity.js create mode 100644 src/entities/DiagramEntity.js create mode 100644 src/entities/WorkspaceEntity.js create mode 100644 src/exceptions/GlobalErrorHandler.js create mode 100644 src/index.css create mode 100644 src/main.jsx create mode 100644 src/services/DataAnalysisService.js create mode 100644 src/services/DataService.js create mode 100644 src/services/DiagramService.js create mode 100644 src/services/WorkspaceService.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md index 9229f6e..c23eb5a 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# mme-sose24-statistics \ No newline at end of file +# mme-sose24-statistics + +## About the app + +## Setup + +To install the application, run + +```bash +$ git clone https://github.com/UniRegensburg/mme-sose24-statistics.git +$ cd mme-sose24-statistics +$ npm install +``` + +To start, run + +```bash +$ npm run dev +``` + +## Technologies + +- Vite +- React +- D3.js +- Material UI + +## Documentations + +### Developer guidelines + +See [GUIDELINES.md](docs/GUIDELINES.md) diff --git a/docs/GUIDELINES.md b/docs/GUIDELINES.md new file mode 100644 index 0000000..d6676bc --- /dev/null +++ b/docs/GUIDELINES.md @@ -0,0 +1,43 @@ +# Developer Guidelines + +## Directory Structure + +### Root folder + +- **`src/`**: Contains all source files. +- **`docs/`**: Documentations, screenshots, etc. +- **`public/`**: Assets that are not referenced in the source code. + +### `src/` folder + +- **`app/pages/`**: Contains subfolders. Each subfolders contains all specialized components used in a single page. +- **`components/`**: Reusable React components. + +- **`services/`**: Service classes responsible for complex utility logics. +- **`entities/`**: Data models and schemas. +- **`exceptions/`**: Custom exceptions and exception handlers. +- **`utils/`**: Utility functions and helpers. +- **`assets/`**: Static resources. Pictures, icons, or pre-defined JSON files, etc. + +## Code Style Conventions + +- **Indentation**: Use 2 spaces for indentation. + +- **Naming:** Use `camelCase` for variables and functions, `PascalCase` for classes and file names. + +- **Comments**: Use JSDoc for React components. For instance: + + ```jsx + /** + * A greeting message to the user. + * + * @param {string} name The user's name. + * @param {number} id User's ID. + * @returns {ReactNode} A React element that renders a greeting to the user. + */ + function Greeting({ name, id }) { + return
Hello, {name}, you are our {id}'s user!
; + } + ``` + + \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..964a299 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,38 @@ +import js from '@eslint/js' +import globals from 'globals' +import react from 'eslint-plugin-react' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { + files: ['**/*.{js,jsx}'], + ignores: ['dist'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + settings: { react: { version: '18.3' } }, + plugins: { + react, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + ...reactHooks.configs.recommended.rules, + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/index.html b/index.html new file mode 100644 index 0000000..0c589ec --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..024e110 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4648 @@ +{ + "name": "usability-statistic", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "usability-statistic", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@mui/material": "^5.16.6", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.8.0", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "vite": "^5.4.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "dependencies": { + "@babel/types": "^7.25.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz", + "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz", + "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz", + "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.9.0", + "@emotion/utils": "^1.4.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz", + "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", + "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", + "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz", + "integrity": "sha512-kytg6LheUG42V8H/o/Ptz3olSO5kUXW9zF0ox18VnblX6bO2yif1FPItgc3ey1t5ansb1+gbe7SatntqusQupg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.6.tgz", + "integrity": "sha512-0LUIKBOIjiFfzzFNxXZBRAyr9UQfmTAFzbt6ziOU2FDXhorNN2o3N9/32mNJbCA8zJo2FqFU6d3dtoqUDyIEfA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.16.6", + "@mui/system": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@mui/private-theming": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.6", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.6.tgz", + "integrity": "sha512-5xgyJjBIMPw8HIaZpfbGAaFYPwImQn7Nyh+wwKWhvkoIeDosQ1ZMVrbTclefi7G8hNmqhip04duYwYpbBFnBgw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.15", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz", + "integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", + "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.8.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0-rc-fb9a90fa48-20240614", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", + "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz", + "integrity": "sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", + "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.40", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f357c6 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "usability-statistic", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@mui/material": "^5.16.6", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.8.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.8.0", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "vite": "^5.4.0" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/App.jsx b/src/app/App.jsx new file mode 100644 index 0000000..2faa7a6 --- /dev/null +++ b/src/app/App.jsx @@ -0,0 +1,11 @@ +import Home from './pages/home/Home' + +function App() { + return ( + <> + + + ) +} + +export default App diff --git a/src/app/pages/home/Home.css b/src/app/pages/home/Home.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/src/app/pages/home/Home.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/app/pages/home/Home.jsx b/src/app/pages/home/Home.jsx new file mode 100644 index 0000000..1b9f204 --- /dev/null +++ b/src/app/pages/home/Home.jsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from '../../../assets/react.svg' +import viteLogo from '/vite.svg' +import './Home.css' + +function Home() { + const [count, setCount] = useState(0) + + return ( + <> +
+ + Vite logo + + + React logo + +
+

Vite + React

+
+ +

+ Edit src/app/pages/home/Home.jsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default Home diff --git a/src/app/pages/workspace/DataTable.jsx b/src/app/pages/workspace/DataTable.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/workspace/Workspace.css b/src/app/pages/workspace/Workspace.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/workspace/modification/ModSubcomponents.jsx b/src/app/pages/workspace/modification/ModSubcomponents.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Diagram.jsx b/src/components/Diagram.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js new file mode 100644 index 0000000..e69de29 diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js new file mode 100644 index 0000000..e69de29 diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js new file mode 100644 index 0000000..e69de29 diff --git a/src/exceptions/GlobalErrorHandler.js b/src/exceptions/GlobalErrorHandler.js new file mode 100644 index 0000000..e69de29 diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..99657a7 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './app/App.jsx' +import './index.css' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js new file mode 100644 index 0000000..e69de29 diff --git a/src/services/DataService.js b/src/services/DataService.js new file mode 100644 index 0000000..e69de29 diff --git a/src/services/DiagramService.js b/src/services/DiagramService.js new file mode 100644 index 0000000..e69de29 diff --git a/src/services/WorkspaceService.js b/src/services/WorkspaceService.js new file mode 100644 index 0000000..e69de29 diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..5a33944 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) From 4ea1618c9693c5e46f499eba54c3bc158dde99de Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 19 Aug 2024 20:27:35 +0200 Subject: [PATCH 02/65] Set up component structure --- docs/GUIDELINES.md | 4 +- package-lock.json | 51 ++++++++++++++++++- package.json | 4 +- src/app/App.jsx | 13 +++-- src/app/pages/home/Home.jsx | 15 ++++++ src/app/pages/workspace/DataTable.jsx | 0 src/app/pages/workspace/Workspace.css | 20 ++++++++ src/app/pages/workspace/Workspace.jsx | 38 ++++++++++++++ .../workspace/datatables/DataTableTabs.jsx | 11 ++++ .../workspace/modification/Modification.jsx | 13 +++++ .../workspace/visualization/Visualization.jsx | 11 ++++ src/components/DataTable.jsx | 11 ++++ src/entities/DataEntity.js | 3 ++ src/entities/DiagramEntity.js | 3 ++ src/entities/WorkspaceEntity.js | 3 ++ 15 files changed, 192 insertions(+), 8 deletions(-) delete mode 100644 src/app/pages/workspace/DataTable.jsx create mode 100644 src/app/pages/workspace/datatables/DataTableTabs.jsx create mode 100644 src/app/pages/workspace/visualization/Visualization.jsx create mode 100644 src/components/DataTable.jsx diff --git a/docs/GUIDELINES.md b/docs/GUIDELINES.md index d6676bc..f20e9cd 100644 --- a/docs/GUIDELINES.md +++ b/docs/GUIDELINES.md @@ -33,10 +33,10 @@ * * @param {string} name The user's name. * @param {number} id User's ID. - * @returns {ReactNode} A React element that renders a greeting to the user. + * @returns {ReactNode} Rendered div component */ function Greeting({ name, id }) { - return
Hello, {name}, you are our {id}'s user!
; + return
Hello {name}, you are our {id}-th user!
; } ``` diff --git a/package-lock.json b/package-lock.json index 024e110..9c90233 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@emotion/styled": "^11.13.0", "@mui/material": "^5.16.6", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-resizable-panels": "^2.0.23", + "react-router-dom": "^6.26.0" }, "devDependencies": { "@eslint/js": "^9.8.0", @@ -1265,6 +1267,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", @@ -3875,6 +3885,45 @@ "node": ">=0.10.0" } }, + "node_modules/react-resizable-panels": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.23.tgz", + "integrity": "sha512-8ZKTwTU11t/FYwiwhMdtZYYyFxic5U5ysRu2YwfkAgDbUJXFvnWSJqhnzkSlW+mnDoNAzDCrJhdOSXBPA76wug==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-router": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", + "dependencies": { + "@remix-run/router": "1.19.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", + "dependencies": { + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/package.json b/package.json index 3f357c6..63ad39e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "@emotion/styled": "^11.13.0", "@mui/material": "^5.16.6", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-resizable-panels": "^2.0.23", + "react-router-dom": "^6.26.0" }, "devDependencies": { "@eslint/js": "^9.8.0", diff --git a/src/app/App.jsx b/src/app/App.jsx index 2faa7a6..f0be19b 100644 --- a/src/app/App.jsx +++ b/src/app/App.jsx @@ -1,11 +1,16 @@ import Home from './pages/home/Home' +import Workspace from './pages/workspace/Workspace' +import { BrowserRouter, Routes, Route } from "react-router-dom"; function App() { return ( - <> - - + + + } /> + } /> + + ) } -export default App +export default App \ No newline at end of file diff --git a/src/app/pages/home/Home.jsx b/src/app/pages/home/Home.jsx index 1b9f204..ea9059f 100644 --- a/src/app/pages/home/Home.jsx +++ b/src/app/pages/home/Home.jsx @@ -2,7 +2,14 @@ import { useState } from 'react' import reactLogo from '../../../assets/react.svg' import viteLogo from '/vite.svg' import './Home.css' +import { Link } from 'react-router-dom' + +/** + * Main component for home page. + * + * @returns Rendered react component. + */ function Home() { const [count, setCount] = useState(0) @@ -24,6 +31,14 @@ function Home() {

Edit src/app/pages/home/Home.jsx and save to test HMR

+ + {/* use Link for redirecting */} + + + +

Click on the Vite and React logos to learn more diff --git a/src/app/pages/workspace/DataTable.jsx b/src/app/pages/workspace/DataTable.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/pages/workspace/Workspace.css b/src/app/pages/workspace/Workspace.css index e69de29..1edc1d5 100644 --- a/src/app/pages/workspace/Workspace.css +++ b/src/app/pages/workspace/Workspace.css @@ -0,0 +1,20 @@ +.container { + position: fixed; + padding: 0; + margin: 0; + + top: 0; + left: 0; + + width: 100%; + height: 100%; + display: flex; + height: 100%; + width: 100%; +} + +.panel { + margin: 0.1em; + background-color: rgb(244, 244, 244); + border-radius: 2%; +} \ No newline at end of file diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx index e69de29..45d3e2b 100644 --- a/src/app/pages/workspace/Workspace.jsx +++ b/src/app/pages/workspace/Workspace.jsx @@ -0,0 +1,38 @@ +import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels" +import "./Workspace.css" +import DataTable from "./datatables/DataTableTabs" +import Visualization from "./visualization/Visualization" +import Modification from "./modification/Modification" +import WorkspaceEntity from "../../../entities/WorkspaceEntity" + +/** + * Main user interface for data analysis. Contains `DataTable`, + * `Visualization` and `Modification` as sub-components. + * + * @param {WorkspaceEntity} workspaceEntity This argument should be a `WorkspaceEntity`. + * @returns {ReactNode} Rendered React component. + */ +export default function Workspace(workspaceEntity) { + return ( + + + + + {/* data table component */} + + + + + {/* visualization component */} + + + + + + + {/* modification component */} + + + + ) +} diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx new file mode 100644 index 0000000..6c30965 --- /dev/null +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -0,0 +1,11 @@ + +/** + * A tabs component containing data tables from a workspace. + * + * @returns {ReactNode} Rendered tabs component. + */ +export default function DataTableTabs() { + return ( +

Data Table

+ ) +} \ No newline at end of file diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index e69de29..41a51fe 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -0,0 +1,13 @@ + +/** + * The user interface for modifying diagrams. + * + * @returns {ReactNode} Rendered component. + */ +function Modification() { + return ( +

Modification

+ ) +} + +export default Modification \ No newline at end of file diff --git a/src/app/pages/workspace/visualization/Visualization.jsx b/src/app/pages/workspace/visualization/Visualization.jsx new file mode 100644 index 0000000..2143b34 --- /dev/null +++ b/src/app/pages/workspace/visualization/Visualization.jsx @@ -0,0 +1,11 @@ + +/** + * A component containing diagrams from a workspace. + * + * @returns {ReactNode} Rendered component. + */ +export default function Visualization() { + return ( +

Visualization

+ ) +} diff --git a/src/components/DataTable.jsx b/src/components/DataTable.jsx new file mode 100644 index 0000000..9e54c4b --- /dev/null +++ b/src/components/DataTable.jsx @@ -0,0 +1,11 @@ +import DataEntity from "../entities/DataEntity"; + +/** + * A table element that present a data entity + * + * @param {DataEntity} dataEntity The data entity to be presented. + * @returns {ReactNode} Rendered table component. + */ +export default function DataTable(dataEntity) { + +} \ No newline at end of file diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index e69de29..bf4dbfb 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -0,0 +1,3 @@ +export default class DataEntity { + +} \ No newline at end of file diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index e69de29..9fc33b6 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -0,0 +1,3 @@ +export default class DiagramEntity { + +} \ No newline at end of file diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js index e69de29..349ca28 100644 --- a/src/entities/WorkspaceEntity.js +++ b/src/entities/WorkspaceEntity.js @@ -0,0 +1,3 @@ +export default class WorkspaceEntity { + +} \ No newline at end of file From 211189ae904d5ee8645ebb1dd7b2ae24a0e48588 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sun, 25 Aug 2024 11:09:46 +0200 Subject: [PATCH 03/65] Fix structure --- src/entities/DataEntity.js | 44 +++++++++++++++++++++++++++-- src/services/DataAnalysisService.js | 4 +++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index bf4dbfb..71c7af5 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,3 +1,43 @@ -export default class DataEntity { +/** + * A class for representing questionnaire data. + */ +class DataEntity { + + /** + * Construct empty DataEntity + */ + constructor() { + this.type = null + this.userInfo = null + this.result = null + } + + /** + * Check if the data is valid according to the type. + * @return {boolean} + */ + dataIsValid() { + return true + } -} \ No newline at end of file +} + + +/** + * A class that contains information about users who took + * the questionnaire. + */ +class UserInfo { + +} + + +/** + * Questionnaire results. + */ +class Result { + +} + + +export {DataEntity, UserInfo, Result} \ No newline at end of file diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index e69de29..d11b62d 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -0,0 +1,4 @@ + +export default class DataAnalysisService { + +} \ No newline at end of file From e9f3345541104f385ee45d4d274eb1e557a1177a Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Thu, 5 Sep 2024 16:14:58 +0200 Subject: [PATCH 04/65] Build SUS-DataStructure --- src/entities/DataEntity.js | 42 ++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 71c7af5..b04e0bc 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -14,10 +14,14 @@ class DataEntity { /** * Check if the data is valid according to the type. + * DO WE NEED THAT?? * @return {boolean} */ dataIsValid() { - return true + if (this.type && this.userInfo && this.result) { + return true; + } + return false; } } @@ -28,16 +32,42 @@ class DataEntity { * the questionnaire. */ class UserInfo { + constructor(userID, age, gender, education) { + this.userID = userID; + this.age = age; + this.gender = gender; + this.education = education; + } } +class SUSResult { + constructor(userID) { + this.userID = userID; // Reference to class UserInfo + this.susScores = Array(10).fill(null); // Array to hold scores for the 10 SUS questions (1 to 5 scale) + } -/** - * Questionnaire results. - */ -class Result { + /** + * Set a SUS score for a specific question. + * @param {number} questionNr - Index of the question (0-9) + * @param {number} score - Score for the question (1-5) + */ + setSUSScore(questionNr, score) { + if (questionNr >= 0 && questionNr < 10 && score >= 1 && score <= 5) { + this.susScores[questionNr] = score; + } else { + console.error("Problem by the SUS-Structure"); + } + } +} +class Result { + constructor(userID, susResult) { + this.userID = userID; + this.susResult = susResult; + } } + -export {DataEntity, UserInfo, Result} \ No newline at end of file +export {DataEntity, UserInfo, SUSResult, Result}; \ No newline at end of file From 7db5dbb22d0031caed3e157094ead254537bc79d Mon Sep 17 00:00:00 2001 From: SebSch80890 <154243692+SebSch80890@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:18:44 +0200 Subject: [PATCH 05/65] Update DataEntity.js --- src/entities/DataEntity.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index b04e0bc..e689eb8 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -44,13 +44,13 @@ class UserInfo { class SUSResult { constructor(userID) { this.userID = userID; // Reference to class UserInfo - this.susScores = Array(10).fill(null); // Array to hold scores for the 10 SUS questions (1 to 5 scale) + this.susScores = Array(10).fill(null); // Array of scores for the 10 SUS questions (1-5) } /** * Set a SUS score for a specific question. - * @param {number} questionNr - Index of the question (0-9) - * @param {number} score - Score for the question (1-5) + * @param {number} questionNr - Nr of question (0-9) + * @param {number} score - Score of question (1-5) */ setSUSScore(questionNr, score) { if (questionNr >= 0 && questionNr < 10 && score >= 1 && score <= 5) { @@ -70,4 +70,4 @@ class Result { -export {DataEntity, UserInfo, SUSResult, Result}; \ No newline at end of file +export {DataEntity, UserInfo, SUSResult, Result}; From beb779fbbefc545f32f12b99708c9b93e8bce5ee Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Wed, 11 Sep 2024 16:31:38 +0200 Subject: [PATCH 06/65] Build SUS-Calculation --- src/entities/DataEntity.js | 2 +- src/services/DataAnalysisService.js | 47 +++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index e689eb8..b115752 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -56,7 +56,7 @@ class SUSResult { if (questionNr >= 0 && questionNr < 10 && score >= 1 && score <= 5) { this.susScores[questionNr] = score; } else { - console.error("Problem by the SUS-Structure"); + console.error("Problem by SUS-Structure"); } } } diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index d11b62d..fd70ddd 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -1,4 +1,47 @@ -export default class DataAnalysisService { +/**export default class DataAnalysisService { -} \ No newline at end of file +}**/ + +// made it this way, maybe we will put here more Calculators for Questionaire + +// SUSEvaluator.js + +class SUSCalcuator { + static calculateSUSScore(susResult) { + if (!susResult || !Array.isArray(susResult.susScores) || susResult.susScores.length !== 10) { + console.error("Problem by SUS-Structure"); + } + else{ + + let score = 0; + + // SUS-Score + for (let i = 0; i < 10; i++) { + if (i % 2 === 0) { // Fragen 1, 3, 5, 7, 9 + score += susResult.susScores[i] - 1; // Rules for odd-numbered + } else { // Fragen 2, 4, 6, 8, 10 + score += 5 - susResult.susScores[i]; // Rules for even-numbered + } + } + } + //final rule + return score * 2.5; + + } + + //Describe SUS + static interpretSUSScore(susScore) { + if (susScore == 100) { + return "Perfect"; + } else if (susScore >= 70) { + return "Good"; + } else if (susScore >= 50) { + return "OK"; + } else { + return "Poor"; + } + } +} + +export {SUSCalcuator}; From e1e9ae7ff93fc7777f5a27c2273285e5db7ad9ab Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 13 Sep 2024 19:11:21 +0800 Subject: [PATCH 07/65] Refactoring --- src/constants/questionnaire-type.js | 74 +++++++++++ src/entities/DataEntity.js | 120 ++++++++++-------- src/exceptions/DataExceptions.js | 18 +++ src/services/DataAnalysisService.js | 47 ------- .../DataAnalysisService.js | 22 ++++ .../data-analysis-service/ScoreCalculators.js | 47 +++++++ src/utils/MathUtils.js | 13 ++ 7 files changed, 238 insertions(+), 103 deletions(-) create mode 100644 src/constants/questionnaire-type.js create mode 100644 src/exceptions/DataExceptions.js delete mode 100644 src/services/DataAnalysisService.js create mode 100644 src/services/data-analysis-service/DataAnalysisService.js create mode 100644 src/services/data-analysis-service/ScoreCalculators.js create mode 100644 src/utils/MathUtils.js diff --git a/src/constants/questionnaire-type.js b/src/constants/questionnaire-type.js new file mode 100644 index 0000000..dc1d465 --- /dev/null +++ b/src/constants/questionnaire-type.js @@ -0,0 +1,74 @@ +import { UndefinedEvaluationError } from "../exceptions/DataExceptions"; + + +/** + * Contains necessary information of all available questionnaires types. + * The following types are currently available: + * @param {object} NONE + * @param {object} SUS + * + * Each type must include following information: + * @param {string} name Name of the type. + * @param {number} numOfQuestions Number of questions contained in the questionnaire. + * @param {number} minValue Minimum result value. + * @param {number} maxValue Maximum result value. + * @param {function} scoreCalculator A function that calculates score for a single row of result. + * @param {function} scoreInterpretor A function that interprets score. + */ +const QUESTIONNAIRE_TYPE = {} + + +QUESTIONNAIRE_TYPE.NONE = { + name: "NONE", + numOfQuestions: Number.MAX_SAFE_INTEGER, + minValue: Number.MIN_SAFE_INTEGER, + maxValue: Number.MAX_SAFE_INTEGER, + scoreCalculator: () => { + throw new UndefinedEvaluationError("Score calculator is not defined for NONE-type questionnaire.") + }, + scoreInterpretor: () => { + throw new UndefinedEvaluationError("Score interpretor is not defined for NONE-type questionnaire.") + } +} + + +QUESTIONNAIRE_TYPE.SUS = { + name: "SUS", + numOfQuestions: 10, + minValue: 1, + maxValue: 5, + + scoreCalculator: (susResult) => { + let score = 0; + + // SUS-Score + for (let i = 0; i < 10; i++) { + if (i % 2 === 0) { // Fragen 1, 3, 5, 7, 9 + score += susResult[`Q${i}`] - 1; // Rules for odd-numbered + } else { // Fragen 2, 4, 6, 8, 10 + score += 5 - susResult[`Q${i}`]; // Rules for even-numbered + } + } + //final rule + return score * 2.5; + }, + + scoreInterpretor: (susScore) => { + if (susScore == 100) { + return "Perfect"; + } else if (susScore >= 70) { + return "Good"; + } else if (susScore >= 50) { + return "OK"; + } else { + return "Poor"; + } + } +} + + + + +Object.freeze(QUESTIONNAIRE_TYPE) + +export default QUESTIONNAIRE_TYPE \ No newline at end of file diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index b115752..90f9765 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,29 +1,64 @@ +import QUESTIONNAIRE_TYPE from "../constants/questionnaire-type" +import { InvalidDataInputError } from "../exceptions/DataExceptions" + + /** - * A class for representing questionnaire data. + * A class for representing a series of questionnaire data. + * */ class DataEntity { - /** - * Construct empty DataEntity - */ - constructor() { - this.type = null - this.userInfo = null - this.result = null - } + /** + * Constructor + * @param {object} type Type of questionnaire. `DATA_TYPE.NONE` by default. + * @param {UserInfo[]} userInfos A list of users' information. `null` by default. + * @param {object[]} results A list of questionnaire results. `null` by default. + */ + constructor(type = QUESTIONNAIRE_TYPE.NONE, userInfos = [], results = []) { + this.type = type + this.userInfos = userInfos + this.results = results + } - /** - * Check if the data is valid according to the type. - * DO WE NEED THAT?? - * @return {boolean} - */ - dataIsValid() { - if (this.type && this.userInfo && this.result) { - return true; - } - return false; + /** + * Set questionnaire result value. + * @param {number} rowNr Target row. + * @param {number} questionNr Target question number. + * @param {number} value Value to change to. + * @returns + */ + setResultValue(rowNr, questionNr, value) { + if (value < this.type.minValue || this.type.maxValue < value) { + throw new InvalidDataInputError(`Error at row ${rowNr}. Input value should be between + ${this.type.minValue} and ${this.type.maxValue}. Your input value was ${value}.`) } - + if (questionNr < 0 || this.type.numOfQuestions <= questionNr) { + throw new InvalidDataInputError(`Error at row ${rowNr}. Question number should be bewteen + 0 and ${this.type.numOfQuestions - 1}. Your question number was ${questionNr}.`) + } + this.results[rowNr][`Q${questionNr}`] = value + } + + insertEmptyRow(index) { + this.userInfos.splice(index, 0, {}) + this.results.splice(index, 0, {}) + } + + getSize() { + return this.userInfos.length + } + + + /** + * Check if the data is valid according to the type. + * @return {boolean} + */ + isValid() { + return ( + this.userInfos.length === this.results.length + ) + } + } @@ -31,43 +66,16 @@ class DataEntity { * A class that contains information about users who took * the questionnaire. */ -class UserInfo { - constructor(userID, age, gender, education) { - this.userID = userID; - this.age = age; - this.gender = gender; - this.education = education; - } +// class UserInfo { -} - -class SUSResult { - constructor(userID) { - this.userID = userID; // Reference to class UserInfo - this.susScores = Array(10).fill(null); // Array of scores for the 10 SUS questions (1-5) - } - - /** - * Set a SUS score for a specific question. - * @param {number} questionNr - Nr of question (0-9) - * @param {number} score - Score of question (1-5) - */ - setSUSScore(questionNr, score) { - if (questionNr >= 0 && questionNr < 10 && score >= 1 && score <= 5) { - this.susScores[questionNr] = score; - } else { - console.error("Problem by SUS-Structure"); - } - } -} +// constructor(userID=null, age=null, gender=null, education=null) { +// this.userID = userID; +// this.age = age; +// this.gender = gender; +// this.education = education; +// } -class Result { - constructor(userID, susResult) { - this.userID = userID; - this.susResult = susResult; - } -} - +// } -export {DataEntity, UserInfo, SUSResult, Result}; +export { DataEntity }; diff --git a/src/exceptions/DataExceptions.js b/src/exceptions/DataExceptions.js new file mode 100644 index 0000000..9037702 --- /dev/null +++ b/src/exceptions/DataExceptions.js @@ -0,0 +1,18 @@ +class InvalidDataInputError extends Error { + constructor(message) { + super(message) + this.name = this.constructor.name + } +} + +class UndefinedEvaluationError extends Error { + constructor(message) { + super(message) + this.name = this.constructor.name + } +} + +export { + InvalidDataInputError, + UndefinedEvaluationError +} \ No newline at end of file diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js deleted file mode 100644 index fd70ddd..0000000 --- a/src/services/DataAnalysisService.js +++ /dev/null @@ -1,47 +0,0 @@ - -/**export default class DataAnalysisService { - -}**/ - -// made it this way, maybe we will put here more Calculators for Questionaire - -// SUSEvaluator.js - -class SUSCalcuator { - static calculateSUSScore(susResult) { - if (!susResult || !Array.isArray(susResult.susScores) || susResult.susScores.length !== 10) { - console.error("Problem by SUS-Structure"); - } - else{ - - let score = 0; - - // SUS-Score - for (let i = 0; i < 10; i++) { - if (i % 2 === 0) { // Fragen 1, 3, 5, 7, 9 - score += susResult.susScores[i] - 1; // Rules for odd-numbered - } else { // Fragen 2, 4, 6, 8, 10 - score += 5 - susResult.susScores[i]; // Rules for even-numbered - } - } - } - //final rule - return score * 2.5; - - } - - //Describe SUS - static interpretSUSScore(susScore) { - if (susScore == 100) { - return "Perfect"; - } else if (susScore >= 70) { - return "Good"; - } else if (susScore >= 50) { - return "OK"; - } else { - return "Poor"; - } - } -} - -export {SUSCalcuator}; diff --git a/src/services/data-analysis-service/DataAnalysisService.js b/src/services/data-analysis-service/DataAnalysisService.js new file mode 100644 index 0000000..0aed5d9 --- /dev/null +++ b/src/services/data-analysis-service/DataAnalysisService.js @@ -0,0 +1,22 @@ +import { DataEntity } from "../../entities/DataEntity" +import { average } from "../../utils/MathUtils" + + +class DataAnalysisService { + + /** + * Calculate average score of a DataEntity according to its questionnaire type. + * @param {DataEntity} dataEntity + */ + calculateScore(dataEntity) { + const calculator = dataEntity.type.scoreCalculator + const scores = dataEntity.results.map(calculator) + console.log(`scores are ${scores}`) + return average(scores) + } +} + +const dataAnalysisService = new DataAnalysisService() + + +export default dataAnalysisService \ No newline at end of file diff --git a/src/services/data-analysis-service/ScoreCalculators.js b/src/services/data-analysis-service/ScoreCalculators.js new file mode 100644 index 0000000..8e80ecf --- /dev/null +++ b/src/services/data-analysis-service/ScoreCalculators.js @@ -0,0 +1,47 @@ +import QUESTIONNAIRE_TYPE from "../../constants/questionnaire-type"; + + +/** + * Find the corresponding calculator according to the questionnaire type. + * @param {object} type Type of questionnaire. + * @returns {function} Corresponding calculator function. + */ +export default function getCalculator(type) { + if (type === QUESTIONNAIRE_TYPE.NONE) { + throw new Error("Evaluation of the data is undefined.") + } + else if (type === QUESTIONNAIRE_TYPE.SUS) { + return calculateSUSScore + } +} + + +function calculateSUSScore(susResult) { + let score = 0; + + // SUS-Score + for (let i = 0; i < 10; i++) { + if (i % 2 === 0) { // Fragen 1, 3, 5, 7, 9 + score += susResult.susScores[i] - 1; // Rules for odd-numbered + } else { // Fragen 2, 4, 6, 8, 10 + score += 5 - susResult.susScores[i]; // Rules for even-numbered + } + } + //final rule + return score * 2.5; + +} + + +//Describe SUS +function interpretSUSScore(susScore) { + if (susScore == 100) { + return "Perfect"; + } else if (susScore >= 70) { + return "Good"; + } else if (susScore >= 50) { + return "OK"; + } else { + return "Poor"; + } +} \ No newline at end of file diff --git a/src/utils/MathUtils.js b/src/utils/MathUtils.js new file mode 100644 index 0000000..1a28dcd --- /dev/null +++ b/src/utils/MathUtils.js @@ -0,0 +1,13 @@ +/** + * Calculate average of an array. + * @param {number[]} arr + * @returns + */ +function average(arr) { + return arr.reduce((a, b) => a + b) / arr.length +} + + +export { + average +} \ No newline at end of file From e1c175f71f644b9f3907a20d540d07c581feaed1 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 13 Sep 2024 19:11:49 +0800 Subject: [PATCH 08/65] Add tests --- package-lock.json | 390 ++++++++++++++++++++- package.json | 6 +- tests/entities/DataEntityService.test.js | 26 ++ tests/services/DataAnalysisService.test.js | 62 ++++ 4 files changed, 481 insertions(+), 3 deletions(-) create mode 100644 tests/entities/DataEntityService.test.js create mode 100644 tests/services/DataAnalysisService.test.js diff --git a/package-lock.json b/package-lock.json index 9c90233..ccdeb43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,8 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", - "vite": "^5.4.0" + "vite": "^5.4.0", + "vitest": "^2.1.0" } }, "node_modules/@ampproject/remapping": { @@ -1530,6 +1531,17 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1585,6 +1597,113 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/expect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.0.tgz", + "integrity": "sha512-N3/xR4fSu0+6sVZETEtPT1orUs2+Y477JOXTcU3xKuu3uBlsgbD7/7Mz2LZ1Jr1XjwilEWlrIgSCj4N1+5ZmsQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.0", + "@vitest/utils": "2.1.0", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.0.tgz", + "integrity": "sha512-ZxENovUqhzl+QiOFpagiHUNUuZ1qPd5yYTCYHomGIZOFArzn4mgX2oxZmiAItJWAaXHG6bbpb/DpSPhlk5DgtA==", + "dev": true, + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.0", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.0.tgz", + "integrity": "sha512-7sxf2F3DNYatgmzXXcTh6cq+/fxwB47RIQqZJFoSH883wnVAoccSRT6g+dTKemUBo8Q5N4OYYj1EBXLuRKvp3Q==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.0.tgz", + "integrity": "sha512-D9+ZiB8MbMt7qWDRJc4CRNNUlne/8E1X7dcKhZVAbcOKG58MGGYVDqAq19xlhNfMFZsW0bpVKgztBwks38Ko0w==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.1.0", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.0.tgz", + "integrity": "sha512-x69CygGMzt9VCO283K2/FYQ+nBrOj66OTKpsPykjCR4Ac3lLV+m85hj9reaIGmjBSsKzVvbxWmjWE3kF5ha3uQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.0", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.0.tgz", + "integrity": "sha512-IXX5NkbdgTYTog3F14i2LgnBc+20YmkXMx0IWai84mcxySUDRgm0ihbOfR4L0EVRBDFG85GjmQQEZNNKVVpkZw==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.0.tgz", + "integrity": "sha512-rreyfVe0PuNqJfKYUwfPDfi6rrp0VSu0Wgvp5WBqJonP+4NvXHk48X6oBam1Lj47Hy6jbJtnMj3OcRdrkTP0tA==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.0", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -1778,6 +1897,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1871,6 +1999,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -1918,6 +2055,22 @@ } ] }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1931,6 +2084,15 @@ "node": ">=4" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2065,6 +2227,15 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2617,6 +2788,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2772,6 +2952,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -3500,6 +3689,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3509,6 +3707,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3758,6 +3965,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -4199,6 +4421,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4216,6 +4444,18 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -4358,6 +4598,45 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4466,6 +4745,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -4564,6 +4851,91 @@ } } }, + "node_modules/vite-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.0.tgz", + "integrity": "sha512-+ybYqBVUjYyIscoLzMWodus2enQDZOpGhcU6HdOVD6n8WZdk12w1GFL3mbnxLs7hPtRtqs1Wo5YF6/Tsr6fmhg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.0.tgz", + "integrity": "sha512-XuuEeyNkqbfr0FtAvd9vFbInSSNY1ykCQTYQ0sj9wPy4hx+1gR7gqVNdW0AX2wrrM1wWlN5fnJDjF9xG6mYRSQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.0", + "@vitest/mocker": "2.1.0", + "@vitest/pretty-format": "^2.1.0", + "@vitest/runner": "2.1.0", + "@vitest/snapshot": "2.1.0", + "@vitest/spy": "2.1.0", + "@vitest/utils": "2.1.0", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.0", + "@vitest/ui": "2.1.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4658,6 +5030,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 63ad39e..f018d33 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest" }, "dependencies": { "@emotion/react": "^11.13.0", @@ -28,6 +29,7 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", - "vite": "^5.4.0" + "vite": "^5.4.0", + "vitest": "^2.1.0" } } diff --git a/tests/entities/DataEntityService.test.js b/tests/entities/DataEntityService.test.js new file mode 100644 index 0000000..6745f2d --- /dev/null +++ b/tests/entities/DataEntityService.test.js @@ -0,0 +1,26 @@ +import { test, expect } from "vitest" +import { DataEntity } from "../../src/entities/DataEntity" +import QUESTIONNAIRE_TYPE from "../../src/constants/questionnaire-type" +import { InvalidDataInputError } from "../../src/exceptions/DataExceptions" + + +const susDataEntity = new DataEntity(QUESTIONNAIRE_TYPE.SUS) +susDataEntity.insertEmptyRow(0) + +test( + "DataEntity throws exception when setting result values is outside of value range.", + () => { + expect(() => susDataEntity.setResultValue(0, 0, 6)) + .toThrow(InvalidDataInputError) + expect(() => susDataEntity.setResultValue(0, 0, 0)) + .toThrow(InvalidDataInputError) + } +) + +test( + "DataEntity throws exception when question number is outside of range.", + () => { + expect(() => susDataEntity.setResultValue(0, 10, 5)) + .toThrow(InvalidDataInputError) + } +) \ No newline at end of file diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js new file mode 100644 index 0000000..802ea37 --- /dev/null +++ b/tests/services/DataAnalysisService.test.js @@ -0,0 +1,62 @@ +import { test, expect, assert } from "vitest"; +import QUESTIONNAIRE_TYPE from "../../src/constants/questionnaire-type"; +import { DataEntity, UserInfo } from "../../src/entities/DataEntity"; +import dataAnalysisService from "../../src/services/data-analysis-service/DataAnalysisService"; +import { experimental_extendTheme } from "@mui/material"; +import { UndefinedEvaluationError } from "../../src/exceptions/DataExceptions"; + + +test( + "DataAnalysisService does not calculate score for NONE-type data.", + () => { + const testDataEntity = generateDataEntity( + QUESTIONNAIRE_TYPE.NONE, + [[5,5,5,5,5,5,5,5,5,5,5,5,5]] + ) + expect(() => dataAnalysisService.calculateScore(testDataEntity)) + .toThrow(UndefinedEvaluationError) + } +) + + +test( + "DataAnalysisService calculates SUS correctly.", + () => { + const testDataEntity = generateDataEntity( + QUESTIONNAIRE_TYPE.SUS, + [ + [3,3,3,3,3,3,3,3,3,3], + [5,5,5,5,5,5,5,5,5,5] + ] + ) + const susScore = dataAnalysisService.calculateScore(testDataEntity) + assert(susScore === 50, "Test SUS data should have SUS score 50.") + } +) + + +/** + * A helper function for generating DataEntity. + * @param {object} type Type of questionnaire. + * @param {number[][]} valueMatrix An matrix of result value. + */ +function generateDataEntity(type, valueMatrix) { + const userInfos = [] + const results = [] + + valueMatrix.forEach((arr) => { + let result = {} + arr.forEach((value, index) => { + result[`Q${index}`] = value + }) + + userInfos.push({}) + results.push(result) + }) + + return new DataEntity( + type, + userInfos, + results + ) +} \ No newline at end of file From 9db2c1cfaceb041d39686d51ccc4f4bdb605bd9e Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 13 Sep 2024 19:20:38 +0800 Subject: [PATCH 09/65] Fix bug --- src/constants/{questionnaire-type.js => QuestionnaireType.js} | 0 src/entities/DataEntity.js | 2 +- tests/entities/DataEntityService.test.js | 2 +- tests/services/DataAnalysisService.test.js | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/constants/{questionnaire-type.js => QuestionnaireType.js} (100%) diff --git a/src/constants/questionnaire-type.js b/src/constants/QuestionnaireType.js similarity index 100% rename from src/constants/questionnaire-type.js rename to src/constants/QuestionnaireType.js diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 90f9765..7fee330 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,4 +1,4 @@ -import QUESTIONNAIRE_TYPE from "../constants/questionnaire-type" +import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { InvalidDataInputError } from "../exceptions/DataExceptions" diff --git a/tests/entities/DataEntityService.test.js b/tests/entities/DataEntityService.test.js index 6745f2d..3db7e6c 100644 --- a/tests/entities/DataEntityService.test.js +++ b/tests/entities/DataEntityService.test.js @@ -1,6 +1,6 @@ import { test, expect } from "vitest" import { DataEntity } from "../../src/entities/DataEntity" -import QUESTIONNAIRE_TYPE from "../../src/constants/questionnaire-type" +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" import { InvalidDataInputError } from "../../src/exceptions/DataExceptions" diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js index 802ea37..2d0aca3 100644 --- a/tests/services/DataAnalysisService.test.js +++ b/tests/services/DataAnalysisService.test.js @@ -1,5 +1,5 @@ import { test, expect, assert } from "vitest"; -import QUESTIONNAIRE_TYPE from "../../src/constants/questionnaire-type"; +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType"; import { DataEntity, UserInfo } from "../../src/entities/DataEntity"; import dataAnalysisService from "../../src/services/data-analysis-service/DataAnalysisService"; import { experimental_extendTheme } from "@mui/material"; From 69dd7a453d57dea6aae0bd1e44b19b00694d330e Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 13 Sep 2024 22:35:52 +0800 Subject: [PATCH 10/65] Enable importing data --- package-lock.json | 459 ++++++++++++++++++ package.json | 1 + src/constants/QuestionnaireType.js | 4 +- src/entities/DataEntity.js | 8 +- .../DataAnalysisService.js | 5 +- src/services/DataService.js | 52 ++ .../data-analysis-service/ScoreCalculators.js | 47 -- tests/entities/DataEntityService.test.js | 4 +- tests/services/DataAnalysisService.test.js | 9 +- tests/services/DataService.test.js | 11 + tests/test-data/SUS-example-generator.xlsx | Bin 0 -> 13031 bytes tests/test-data/SUS-example.csv | 41 ++ 12 files changed, 579 insertions(+), 62 deletions(-) rename src/services/{data-analysis-service => }/DataAnalysisService.js (74%) delete mode 100644 src/services/data-analysis-service/ScoreCalculators.js create mode 100644 tests/services/DataService.test.js create mode 100644 tests/test-data/SUS-example-generator.xlsx create mode 100644 tests/test-data/SUS-example.csv diff --git a/package-lock.json b/package-lock.json index ccdeb43..07a5469 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", + "d3": "^7.9.0", "eslint": "^9.8.0", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", @@ -2114,6 +2115,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2160,6 +2170,407 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dev": true, + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dev": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dev": true, + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dev": true, + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dev": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dev": true, + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dev": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dev": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -2276,6 +2687,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dev": true, + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3136,6 +3556,18 @@ "react-is": "^16.7.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3183,6 +3615,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -4240,6 +4681,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "dev": true + }, "node_modules/rollup": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", @@ -4298,6 +4745,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -4333,6 +4786,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", diff --git a/package.json b/package.json index f018d33..e0fa826 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", + "d3": "^7.9.0", "eslint": "^9.8.0", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", diff --git a/src/constants/QuestionnaireType.js b/src/constants/QuestionnaireType.js index dc1d465..d39e2a2 100644 --- a/src/constants/QuestionnaireType.js +++ b/src/constants/QuestionnaireType.js @@ -42,8 +42,8 @@ QUESTIONNAIRE_TYPE.SUS = { let score = 0; // SUS-Score - for (let i = 0; i < 10; i++) { - if (i % 2 === 0) { // Fragen 1, 3, 5, 7, 9 + for (let i = 1; i <= 10; i++) { + if (i % 2 === 1) { // Fragen 1, 3, 5, 7, 9 score += susResult[`Q${i}`] - 1; // Rules for odd-numbered } else { // Fragen 2, 4, 6, 8, 10 score += 5 - susResult[`Q${i}`]; // Rules for even-numbered diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 7fee330..c2fa209 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -14,7 +14,7 @@ class DataEntity { * @param {UserInfo[]} userInfos A list of users' information. `null` by default. * @param {object[]} results A list of questionnaire results. `null` by default. */ - constructor(type = QUESTIONNAIRE_TYPE.NONE, userInfos = [], results = []) { + constructor(type=QUESTIONNAIRE_TYPE.NONE, userInfos=[], results=[]) { this.type = type this.userInfos = userInfos this.results = results @@ -23,7 +23,7 @@ class DataEntity { /** * Set questionnaire result value. * @param {number} rowNr Target row. - * @param {number} questionNr Target question number. + * @param {number} questionNr Target question number, starting from 1. * @param {number} value Value to change to. * @returns */ @@ -32,9 +32,9 @@ class DataEntity { throw new InvalidDataInputError(`Error at row ${rowNr}. Input value should be between ${this.type.minValue} and ${this.type.maxValue}. Your input value was ${value}.`) } - if (questionNr < 0 || this.type.numOfQuestions <= questionNr) { + if (questionNr <= 0 || this.type.numOfQuestions < questionNr) { throw new InvalidDataInputError(`Error at row ${rowNr}. Question number should be bewteen - 0 and ${this.type.numOfQuestions - 1}. Your question number was ${questionNr}.`) + 1 and ${this.type.numOfQuestions}. Your question number was ${questionNr}.`) } this.results[rowNr][`Q${questionNr}`] = value } diff --git a/src/services/data-analysis-service/DataAnalysisService.js b/src/services/DataAnalysisService.js similarity index 74% rename from src/services/data-analysis-service/DataAnalysisService.js rename to src/services/DataAnalysisService.js index 0aed5d9..1b62fd6 100644 --- a/src/services/data-analysis-service/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -1,5 +1,5 @@ -import { DataEntity } from "../../entities/DataEntity" -import { average } from "../../utils/MathUtils" +import { DataEntity } from "../entities/DataEntity" +import { average } from "../utils/MathUtils" class DataAnalysisService { @@ -11,7 +11,6 @@ class DataAnalysisService { calculateScore(dataEntity) { const calculator = dataEntity.type.scoreCalculator const scores = dataEntity.results.map(calculator) - console.log(`scores are ${scores}`) return average(scores) } } diff --git a/src/services/DataService.js b/src/services/DataService.js index e69de29..9cc2798 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -0,0 +1,52 @@ +import { csvParse } from "d3" +import { readFileSync } from "fs" +import { DataEntity } from "../entities/DataEntity" +import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" + + +class DataService { + + importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { + const rawData = csvParse(readFileSync(filePath).toString()) + const [userInfos, results] = infoResultSplit(rawData) + return new DataEntity(type, userInfos, results) + } + +} + + +/** + * Splitting raw questionnaire data object into user info object and result object. + * @param {object} rawData + * @returns {[object, object]} + */ +function infoResultSplit(rawData) { + const isInfoColumn = rawData.columns.map((value) => value.match(/Q[1-9][0-9]*/g)) + + const userInfos = [] + const results = [] + + rawData.forEach((row) => { + const userInfoRow = {} + const resultRow = {} + + rawData.columns.forEach((value, index) => { + if (isInfoColumn[index]) { + userInfoRow[value] = row[value] + } + else { + resultRow[value] = row[value] + } + }) + userInfos.push(userInfoRow) + results.push(resultRow) + }) + + return [userInfos, results] +} + + + +const dataService = new DataService() + +export default dataService \ No newline at end of file diff --git a/src/services/data-analysis-service/ScoreCalculators.js b/src/services/data-analysis-service/ScoreCalculators.js deleted file mode 100644 index 8e80ecf..0000000 --- a/src/services/data-analysis-service/ScoreCalculators.js +++ /dev/null @@ -1,47 +0,0 @@ -import QUESTIONNAIRE_TYPE from "../../constants/questionnaire-type"; - - -/** - * Find the corresponding calculator according to the questionnaire type. - * @param {object} type Type of questionnaire. - * @returns {function} Corresponding calculator function. - */ -export default function getCalculator(type) { - if (type === QUESTIONNAIRE_TYPE.NONE) { - throw new Error("Evaluation of the data is undefined.") - } - else if (type === QUESTIONNAIRE_TYPE.SUS) { - return calculateSUSScore - } -} - - -function calculateSUSScore(susResult) { - let score = 0; - - // SUS-Score - for (let i = 0; i < 10; i++) { - if (i % 2 === 0) { // Fragen 1, 3, 5, 7, 9 - score += susResult.susScores[i] - 1; // Rules for odd-numbered - } else { // Fragen 2, 4, 6, 8, 10 - score += 5 - susResult.susScores[i]; // Rules for even-numbered - } - } - //final rule - return score * 2.5; - -} - - -//Describe SUS -function interpretSUSScore(susScore) { - if (susScore == 100) { - return "Perfect"; - } else if (susScore >= 70) { - return "Good"; - } else if (susScore >= 50) { - return "OK"; - } else { - return "Poor"; - } -} \ No newline at end of file diff --git a/tests/entities/DataEntityService.test.js b/tests/entities/DataEntityService.test.js index 3db7e6c..8f07c3e 100644 --- a/tests/entities/DataEntityService.test.js +++ b/tests/entities/DataEntityService.test.js @@ -20,7 +20,9 @@ test( test( "DataEntity throws exception when question number is outside of range.", () => { - expect(() => susDataEntity.setResultValue(0, 10, 5)) + expect(() => susDataEntity.setResultValue(0, 11, 5)) + .toThrow(InvalidDataInputError) + expect(() => susDataEntity.setResultValue(0, 0, 5)) .toThrow(InvalidDataInputError) } ) \ No newline at end of file diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js index 2d0aca3..798dc8c 100644 --- a/tests/services/DataAnalysisService.test.js +++ b/tests/services/DataAnalysisService.test.js @@ -1,8 +1,7 @@ import { test, expect, assert } from "vitest"; import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType"; -import { DataEntity, UserInfo } from "../../src/entities/DataEntity"; -import dataAnalysisService from "../../src/services/data-analysis-service/DataAnalysisService"; -import { experimental_extendTheme } from "@mui/material"; +import { DataEntity } from "../../src/entities/DataEntity"; +import dataAnalysisService from "../../src/services/DataAnalysisService"; import { UndefinedEvaluationError } from "../../src/exceptions/DataExceptions"; @@ -38,7 +37,7 @@ test( /** * A helper function for generating DataEntity. * @param {object} type Type of questionnaire. - * @param {number[][]} valueMatrix An matrix of result value. + * @param {number[][]} valueMatrix An matrix of result values. */ function generateDataEntity(type, valueMatrix) { const userInfos = [] @@ -47,7 +46,7 @@ function generateDataEntity(type, valueMatrix) { valueMatrix.forEach((arr) => { let result = {} arr.forEach((value, index) => { - result[`Q${index}`] = value + result[`Q${index + 1}`] = value }) userInfos.push({}) diff --git a/tests/services/DataService.test.js b/tests/services/DataService.test.js new file mode 100644 index 0000000..cbe65e7 --- /dev/null +++ b/tests/services/DataService.test.js @@ -0,0 +1,11 @@ +import { test, assert } from "vitest" +import dataService from "../../src/services/DataService" + +test( + "DataService imports csv correctly,", + () => { + const data = dataService.importData("./tests/test-data/SUS-example.csv") + assert(data.results.length === 40) + assert(data.userInfos.length === 40) + } +) \ No newline at end of file diff --git a/tests/test-data/SUS-example-generator.xlsx b/tests/test-data/SUS-example-generator.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..963add6135dd265209376a4399b20ff02fd073d4 GIT binary patch literal 13031 zcmeIZgRb6#zl;vSyaRKlEL;wIl3CLo!b+CZ~05IVI02}}!w4S(w zy{nnM>sxg%M>7`#W=}g?iac0ox*PyBr2qep|HF5nEU`-mMF0FBI!rN)*gh zZWs>MX+w38Mjto2a)YA1Y`NxL&k>X6Q&R3vfQf7qFrP~GMe=3s)3(rNHYmQxOn_C!yB)aHgSF=m^JaVm3#<@J+2005 z(tIdeJKls3HKT{hNf-q4ICH!n*FET^$i(i5rPRFhSnTkk%c}qh{nS6;1k0|*<&79- zUdVKuJJ-WdeBtv3j<|ORoigc)z_P_O3xrJkQN1q3Zg)C^sTd* ztqTkDulN7y=Ko=Z{FkYhffe8UrJIkTgEzCwahPKA9-=ZWlh8!9$!LI?w#BaUeP!wOF5OT_hPxvUd>*o zNy~ZBxwpl>DsB9nD?haQQfm54q6%}IS&Ik>=MzZ?zF=CQZoi_=it%kZ)U24=(U*|Q z2A-V#gzM0#O9Rfv?e_nyOtN&CIEuA?t zR2Tr@0UiLrfZQ2RTNV!oXB%S&2b*6`tW0CkKA#oS_pQ}4)Lka1=|g~AX<@;F+_J{Z z(GR#{=T0O|Kux`ss>ag|skqF-_NPT=Ioq$DXK861o14>$8bq<`GW`bx>o183YsWmt z_RXT~9hkqeu@_LeDajd569}q=E4^v==Qnhz`smWm4#-JndzENSH3HTVWuS`n6t}5l zZ2TMs`lPK}jg`1noujVt>Bs!_UkobEZIN&{trb zrhwO*Lq3{82X3EYdCi#Yugp5+3SdIiJ<;}m77AF)4uek`OJrU9$=(D~c%q4o<58Aw zXF8CVL3JnJSCF_}|Bg21BYZjjHFD3=_Pr4+H?2Krl+|(R9T=!KfNt1H$QHq_q^n^| zotta2CHfOXL3qf#@*TgmPJ7XrtpkhN_(@3Dbsp)r(UX|Tx}`{ImgrHzQkV9a_;9-y zgl`;D4#qskoizgPNe+Vtce9^zY;^!8hhjD}1*D^lQVs;vg83+o3WBXhrfqpMbvilU zMx~aM`q9#+s|yJDn(ey{QgaEY+9DgjI;Hfl_e3RrS0H%5Z%AUYh;&8lnqOY9Z;nY1 z1DdZHhvvUj@ZP^Kuv`hxRt}5wgeoPsITvBp$*-R7x14JVXFQD2U?xAA5J2$oksIik2W4_%j{wF<2A{+Si*z$@Y!fG zK{Xd)dxkK)crVUCG2Rxh&WEPAI8r91*{Iy^px1TD%+3wgbg1uNt)1Q0{-aFSg_WC5+^AGlM~XqB$v>(+QJPcRHV;s6;_%a9>Ppo%=y$U11X* zXxbUY89Y=QMcLI8o|tASrkwH#TN#A8j0QN){-`|~Nnpp#t)+lyn=k|>s(Xw&>+TTq zEjHelkG3n91srHpG;*c4v!e8D>>b&54R!G2O%SE~lSR>DKizPB$-`NHm)+ct@vKDU z@TM3RdoKiQUFllz)g2+W-)|TCdnsEDx}299@g9?kiv{O(EU3X8;4-j=S&TGpA0fG_ z_k8I{m5qz6YW>LFQvgLj74hLVjOh`jUiyF?r_8kgpaN=t`ye}8$k+R3eKB+~o#pv% zb$=w?t;*2%=_J(O-|gAmK>c7bJ=XW~3yQyY&CmUx&o_&Ct*;;ayj`x@^vecbj2F$h zWYBst}B-Fi@;;+q9`DnnO?U z4n=Uu&}a0c7+>Fj-@`*WjS2V^Wu9ozh#dcF_BM7-@!n|)Qj!uDo0_il9tPrg_3 z23dGd9lYST?mvjCqm+ATjv;2^ZNJz-1l@rpfkjMbfJkNdZX;m?eB zP6;Oo3;Wz%j$iGM6pfds|F{bB*6zX4^?jO4zq-4>rd_6`tGbgvz7T32A8O0IVj(mX zc@Q8CB5&~i+LkEALUBwW(%eyC61p?NyEJs>b-Z;Zsl+bBl`VST`|}C^;udpRfaYcP zgGYI^3Vc`gXnFIBojyBpo0tjcwHRn`kGAAgc*JA&3a0iXE9cQbGyhR7^L+dp*nhjF zvZDMZ{bGoz`Ps1$Mg0BwC8gj~&dr{KkYJZhV&oRgGSF-U3_U*dmkKHH-=FKg=i435TUkXdntuP&>rI#5G>k;0_Rh>8!|R0A|nDE zC6RhuPM0(Ugk_s+SBk1g)RWCEY@=lPpCB8$Z$&}yPDmE-0%yL{$xKiHcfDaym~(J} zmY5V6dyJV7#BLn^Uo9dr5YSu1G|N+G!ytRA4Nk;QDeuGZQxOb^HPMWV^BmZm7&S53 z@r!CWk;Tq)0^wHlN-2OCMB0wOzh;0yJo3gM=M8L>dZ9I^eV(RJAKItX4djF@?UKNJ zL+WQyI7*YMK)9JnI>gC$eDZ(IDH9YY@R`CCf+2|Cku|qwQz1%6Jtuw%J;G8Rt&eui zL^Fnf2N|tjo(DXBnHC))VI&&k>6R`8%ZX3E2y$PL#rlomkcfcPo!uJc=1inoEADBS zAs?m4y|7HipGLZaf~tElxy(;pv2iXDJJy1F zVMUGiq>b@$hZLl=c#oeXwWDYHFj-#8!ecOb>RtGWu`$j&MeHsre2vByhqpw*C{Lyi zk*)TZLb-tv__9SARQtnE#4xir4ws${C@mL7_8G;vuDLMH%6w&p}!)xe@3XlPb%Qc-;^Bfz@Y(#pQiaJPm~e!8S)Euc~r{H2(!Z$BHRgP0u4&W^ZHY3%MvgR zU9Cp6B4J)?=*(#LA3uw?MkxgfbhVw?(a5P94@U3@6~z;W$j$*oK=>{ zbD}L?04OhqnkFw7RW6@=n6tFDlDNCl_`D*I*N|~_w3R&Un_jG=`Wl&0o@fc(GdTK? zIKhf2C@?%u4#Qn-^pkB!XNtff+D4xC z(HTZ7URoSk_MWzQUlXu`hlcJ@Pqpd&lPfs%*BkZfbh7m#V~Wr2>g{-@TC_%y#2;XVvu)GzY*z% z-k=Eoi*fSCe7R>w+~!utY&yrI7^RoZQ=p$xU46GEs4vbrITaFaetyIDaF+Rc@crfc z=-#+Y5`40N`LY=Z(dk{i40Bhzs!v~u~{bDl<-Np^-F$@80$f;Ul$;c2UPGjga- z@$8;_;gFl-7!Qn1HkH%|zQ`^O`bOE_b#J+ zIboCXn~Sd4;xswGU->CIc#z+1O)(8-o}!P@sfMh`+O-``v&?U@7@U4EUDNrvtJ!Cn zr-v63&ivSa%!ENXRn<(%zM$thftEx7)};>F(_W5cF463L|7+g0Sz-bUhu<3#zL*4Q zz}zoDY=?qK6^NS6^tr0Ha^|C7PEnG80vJ9^28=BJO1s*j7cD6jPPRJsC)zi);BRVp z8`|n_XOELYJ@3z-wBvyGCg9Y&aeJ;`r!}j#sqHt6aD>;NHY54~}WIbawMKr!+J}#>B z459Dcv^v6R2ktB?Tp|ENY|j%?r}=wzJ7BA}N*{^DX5^>}{*J2%{rRx%uAq5$3jCV!4IbIi#(_I99(*sc!=QV_X( zN7cPtgxxEr1CN26wWM;stH_9VmZQJ4BX^l2<1^QgU;r&6Y*c&mB$J6tNA{5cFKiaT z9;WLh&8VdtMy2rzQ_q>umQLU`m4l^dQg28JJTY>IzFPK%XZu*+g+`i@_?=%tiRziM z29XknWS=UEiaL`9Md2`No{|t>p||5pc+xFbGv>h2R7-p2 zlK$SJ0!bH)c`-@HYjQkj?w!3KxBTBx$G&hWvx0*-21*s9`Nv2B=|dq;2iBj?qj7PkXBhqx?ey}+A&%`gM}Hwezx^oO#JA+6dr zA<8`H)DrFR(K;pGr`B25Em;}csk+>W{@zp`L4;X~;XU2=)A9AR+CBA)ZMioeQ|%rh znehLNLKTGr7(YPLe*r|lo@M=xLR~G*?95nxzyA(E4|T>J7P)cTG3K8sc8_)IslTJ| zR)+n-v ztCGAC)6%`vR`q;mF*lhN<^&?Gd~-*(-&lni*=C*eCWr9M#x_vRliNfG1iNH@vCk>K zv&NG<+q@Gz3mZS5Kcc>I*DCM*UDK<~7lQtC?)Q^s%Gq85TzM9ar z!pRYanpYg7wZT882){Kb4$BdY;TC5XC4l(1v7afOhV@Gq(@xsPXzS#8@&-Tzu(6w zTLV2S^#dxg(?{s~;YV9=KS7T=MFhXhu2PF)%J-HD!|qU=NNAOJS=^`i2>&GaIT?LR+VlB#K zl~sqw5IaJpWtj|Tbm6+#*EA(`dDa!ILfcgpJ*AdoJ)_3w3E$< zF-*-@FeMIQUSd=7Nl&0c1ajT!qkSJZYkQAJA-h{uf z-3>T1SpGqBu{)1?xk9aOY=Z_@#4Z6(oBeg=#C~I7_Q{hGaWGxhu)vQ2z`^10mcCV? z2VBBF9^fF<4K6XwUcQ_^lvX+=Wr5gqfg(34T~>^thd|LuCG*~BNMpO%pl~LKR=moO zSdM7)Tj*dNZam6@h?)Vt<$i|8QYBWW*E}o}-!ba-y<-|{EA4bUx*H--?DGm%FCGOq9#CXO~Na(`N1%qTz6(A5qs59eK}-e@tdz^Y$zu z>gf2tpeYn{zY>?w5clHGizL9~h+y7+tslM{64SicE7HQyj|#PnzV69PJAaeq#*$rP zw#7NgxT7Ghu~laEVj6>I$fj&qcII7@e8U3wsp`Rk&6gmtx-smOugwmul$JBYW2n~P zvwBqdQD$95Ypq6#2`a4PutoEjN~sM43%|-1?K)?Wb_IjRdTA`_{S6|wAMD0tXu>Kr zp8hB7c5YKdeBZn8PRzuo5d!#qL|Ek+W>S2|I4ryODuQ9J3>gU2A45(wbVVCf_TWYH zxe=`mJGA_faU#PKbmROj3A?dy4e+w{)E~alrkMH;yW4exU#VrQmqaw)Z>g4^{M_2W z#LT2M@}0M@^)~vnyo=7@*D+U8n_sE57phVtd?+niD(JJ%XMud^K^#wxuA5YeFNr67 z34GFQnYuiE&;?4>njtlKCr{&`6 zWozd0D_>fmv24Grv^G&4=^>N=`LewLor}vpBO>FM;>nsPG^+o$Uh5q0u#T z%lFMoF82&r#QhniBed5-^&Kbt!a@Zpb-0!TQ9pFec$*Q~j7uh{wMtR_+uy>PM3v>2 z6o@e-uM5`Pvim>K)Y-GlFMnhU1&Dn|t;k$dK&tBdC~M7f8A3l_TpK^rHB}b2OdutH zz#=QU!AgQ9+tkg}t&aZ;Lsfj&ybK$hOuT;!MmfGl!19+qo74i(&nu01aags4l!mFB5eK+4lTi`tzGb{#XvMUWL0xouE0j@` zq&b!+=8!bvxb*sb#!sC8RZc4wjhC)vE}FA4wT$YfihA{RZ%;)wM#uB58eWqAp`ODb zsiHxP30OS2Ey1%t`9^}{jDOq+orvDXn$cCc)SlHRrYN_jb0C;E7Kg+O8ErjP1w}h& ztbNsiLJqENaoBF`?%+*^Az^duMuA5l=LfImnZgeZ&V!Dyg!Tqrue5(q*!F1IJiEPz z>5xAnxm3$a&M2?V>n=KU%;%uU03kW;tWKxwqqdaCEer>{@|cCJo86m|DjyyeXphWPOOm?ZegK zvZ-HNSdbGhF+vZNJV=&39bxFynme0?#)!o5X*W7i3pLRkdYk_1r(!%-cZCMd;2>I{%1jI^ zi%=RLbK2Uza;bRmy&)YoV>Tj)W&sr|6*+jSgRW1uecZ5QqKn@JFx_eiD-t zOxvJq5m$p?Ow0=>v0?s!R4H1oG`>+av7ILAM5l?765WGIIOy5_G~j9rG?$uZn$t6m zc25^kRk?V6-^b!{IACq8Ea}UkpmEg1jKZae4WX$U+?+3y5Aoz z%X%S!a*u%?_t~d5LAUG^|HpOvniV^;tZ(C?eNEl#wfk8sD~3W$DMh2x?j1tDyzRCa zI~_iP?Z9HeEO(Gut4LYsm&7q6!dIH%PlT7Z< zKU*Hjo}I(98=88_`%m_gdExL+F6_A+kY!fJKk~oyF6O zM9%%DTj?pho(Owy;F$8#)CTMhR#-h(d;CO3>GQat`{2Efqzk$KOrEkU*|F zr^nZ(VJLomH#7c@6Yw%pW)zA*M}i}8k~L@&5XG3sMVVzJB?!Y>9rg`YswWBr-#4fy zVFR6SBqbSSjLSsDCEYg&>5rE$Ft%$6D}_J#v9^JbG`e$;N*9M9nuCuhFkZ5j#+CD* ztrL^H`T2UggH*$qT$Kk4mK67HRIR0NA=X~1tV3FJ z7T^jil(SXV?}@T}bqOD2%iU?`FssUe(6QD{vSw4WRgLUXY2`75HA@{=8XyO!{whZm z$_Y74t?|WRmNIP>x@10c7Apg?b~+|K_sv(eME^ZGk3=wBETUvQ{b>j^8eXF$R2sG> zwWqiWJKkVf07#N0sXR_3STiRs4_UU+bAziQpa}4S?n~!8WYog>dG>&_-K5TP=tz3Z zx08lIxnA=YJ9JCk2dNQPoZp?DwA0edSCpbXl;1~@cs zz_5RW?sxB0K*zM{j6f(rlknT*8q87oYJhKjmgE|?Q7t_&lcVOs0piaUI zy%IM1lB5dSF_tgQO(Y^BBMN7m!Tanet$xCV1(8Zz3a@k{iDbDHRF8Lz8J_@U6(TPs z@v8C^GkL%e%iAlX61powOL@2n8KxN1EjU*rRUz2+mA!_B3|@D|t4HK@Ej+(q7>iqX zAY4))8GTx&$a)lviI~JPef~~bbU?ZW6OIavv5JI)fTma3`An$2;d&q*^!7@QVqt-- z4Ke}8CowX-n(9|Kg2!?3prK5w1}&v1c;gMtuX!c0=!(am(A zZ*SL32xD2Xkk9plze&V(_egSl{3I6j$Om`twe%`T7wh6HObTkXWM3ZxGvUrfRHqjQ zBH^T4Y`F!iVBSHNsye>3U2~{|nd^*rdsaiPZuvh+nWnRn^|9aTi9G&CQS)@N4?b0~OT3vOF2R%SvOY z4R;I8LX+HTQ<<{(%emP4$=>ho3(@2(hC)=I?61F!ypI}!e;f9L>ILC@oL2`P!Jf?I z%O%1*Gt9Xwt>9S{Dk`p zM6D>>ZIW-!*~crWkCA+XXC*y2aT7L6ik`W?fCz7=i|&yHLLgZ$9F) zK*Q`X6xP8JN9HoS@eWcN@={~F!XNdnbtMqkE?yvVY}^iPhEGu*QWF)VLLGq=lgO54 zX$le{v&mlv&3VyRYsv7w!_Nz~?84bZucjw7nWK)l*9vne7A+_Mq7-frjMZJt8}9v} zTU*oTB~YMP;+1A(daWRQfF^N|aOSKez^>4Wql8qkOQNK<^Il^v;!G!x7#uBfT zaGAgSxB=mDz^O~_j%1x3g>fGbFY}|ea%}6!E_{L;j9=(%(e_Ed=E zjs6?uDqO;O{)apQ3-pT7QdL1OF2JGxGWq;m>H~Z-fZy z-w6K}oBS#L?+*EISpcB#6#(!b?)gvge>bN8DlWtHFXI0&t;+In5N`Y`jYk3e%B0}1 I{`&O)06H%E!vFvP literal 0 HcmV?d00001 diff --git a/tests/test-data/SUS-example.csv b/tests/test-data/SUS-example.csv new file mode 100644 index 0000000..fa0efc0 --- /dev/null +++ b/tests/test-data/SUS-example.csv @@ -0,0 +1,41 @@ +id,age,gender,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10 +1,37,F,4,3,5,3,4,2,2,2,2,4 +2,41,M,4,2,2,3,2,3,3,2,2,3 +3,12,M,4,1,4,1,4,1,5,1,5,1 +4,29,M,3,2,5,1,5,2,3,2,5,2 +5,50,M,2,3,1,4,4,4,4,3,3,3 +6,19,F,4,1,4,1,3,2,5,1,4,1 +7,36,M,4,2,3,1,3,2,3,3,5,2 +8,61,M,3,4,2,4,1,4,1,3,3,4 +9,45,F,2,4,1,3,3,4,3,4,2,4 +10,25,F,3,2,5,3,5,2,4,3,3,2 +11,38,F,5,3,3,2,2,2,5,3,5,3 +12,56,F,2,4,2,4,1,5,2,5,3,3 +13,33,M,4,3,4,3,5,2,3,3,4,2 +14,25,F,5,1,5,2,3,3,5,2,4,2 +15,28,F,5,2,5,1,3,3,5,3,4,3 +16,40,F,4,3,4,2,2,2,3,3,3,3 +17,20,M,5,2,4,1,5,1,5,1,5,1 +18,29,M,5,1,5,2,3,1,5,2,4,1 +19,27,F,5,3,5,2,5,2,3,3,5,2 +20,37,M,4,2,3,2,3,2,4,2,3,3 +21,29,F,5,3,4,2,4,3,5,2,2,3 +22,34,F,4,2,3,2,3,2,5,3,2,2 +23,44,M,5,2,4,2,3,3,4,3,3,4 +24,45,M,5,3,5,3,4,2,5,3,5,3 +25,52,F,1,4,1,4,2,4,1,4,2,4 +26,17,F,5,2,3,2,5,3,5,2,3,1 +27,23,F,5,2,5,2,5,2,3,2,5,1 +28,26,M,4,2,5,1,5,1,5,2,4,1 +29,49,M,3,3,1,3,5,3,5,4,2,3 +30,36,F,4,3,2,3,5,2,3,2,2,2 +31,28,F,2,3,5,3,5,2,3,3,3,2 +32,35,M,4,1,4,1,2,1,5,3,2,3 +33,19,M,5,1,5,1,3,2,5,1,5,1 +34,20,F,5,2,4,2,4,2,5,2,5,2 +35,28,M,5,2,3,1,3,2,5,2,4,1 +36,46,F,5,3,1,4,2,4,4,4,1,4 +37,37,F,4,2,3,3,3,2,4,3,4,4 +38,30,F,4,2,3,3,5,2,5,3,5,2 +39,22,F,5,3,5,2,3,2,4,1,3,2 +40,26,M,5,2,5,1,5,2,4,1,3,1 From 1f7b7d2c90ded554c7aec94ff6ae4eeba4346163 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sat, 14 Sep 2024 15:45:43 +0800 Subject: [PATCH 11/65] Enable adding columns in DataEntity --- src/constants/QuestionnaireType.js | 6 +- src/entities/DataEntity.js | 68 +++++++++++++++--- src/exceptions/DataExceptions.js | 4 +- src/services/DataAnalysisService.js | 20 ++++-- src/services/DataService.js | 32 +-------- src/utils/DataUtils.js | 73 +++++++++++++++++++ tests/TestUtils.js | 12 ++++ tests/entities/DataEntity.test.js | 83 ++++++++++++++++++++++ tests/entities/DataEntityService.test.js | 28 -------- tests/services/DataAnalysisService.test.js | 8 +-- tests/services/DataService.test.js | 16 +++++ 11 files changed, 266 insertions(+), 84 deletions(-) create mode 100644 src/utils/DataUtils.js create mode 100644 tests/TestUtils.js create mode 100644 tests/entities/DataEntity.test.js delete mode 100644 tests/entities/DataEntityService.test.js diff --git a/src/constants/QuestionnaireType.js b/src/constants/QuestionnaireType.js index d39e2a2..a806395 100644 --- a/src/constants/QuestionnaireType.js +++ b/src/constants/QuestionnaireType.js @@ -1,4 +1,4 @@ -import { UndefinedEvaluationError } from "../exceptions/DataExceptions"; +import { QuestionnaireTypeError } from "../exceptions/DataExceptions"; /** @@ -24,10 +24,10 @@ QUESTIONNAIRE_TYPE.NONE = { minValue: Number.MIN_SAFE_INTEGER, maxValue: Number.MAX_SAFE_INTEGER, scoreCalculator: () => { - throw new UndefinedEvaluationError("Score calculator is not defined for NONE-type questionnaire.") + throw new QuestionnaireTypeError("Score calculator is not defined for NONE-type questionnaire.") }, scoreInterpretor: () => { - throw new UndefinedEvaluationError("Score interpretor is not defined for NONE-type questionnaire.") + throw new QuestionnaireTypeError("Score interpretor is not defined for NONE-type questionnaire.") } } diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index c2fa209..7bc027b 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,5 +1,6 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" -import { InvalidDataInputError } from "../exceptions/DataExceptions" +import { InvalidDataInputError, QuestionnaireTypeError } from "../exceptions/DataExceptions" +import { generateEmptyRow, generateResultColumns } from "../utils/DataUtils" /** @@ -18,6 +19,37 @@ class DataEntity { this.type = type this.userInfos = userInfos this.results = results + + if (!this.userInfos || userInfos.length === 0) this.userInfosColumns = [] + else this.userInfosColumns = Object.keys(userInfos[0]) + + if (!this.results || this.results.length === 0) this.resultsColumns = generateResultColumns(type) + else this.resultsColumns = Object.keys(results[0]) + } + + addQuestions(numOfNewQuestions=1) { + if (this.type !== QUESTIONNAIRE_TYPE.NONE) { + throw new QuestionnaireTypeError("Only NONE-type data allows adding new questions.") + } + for (let i = 0; i < numOfNewQuestions; i++) { + const newCol = `Q${this.numOfQuestions + 1}` + this.resultsColumns.push(newCol) + this.results.forEach(row => row[newCol] = null) + } + } + + /** + * Given a string or an array of string, add new strings among them as user info columns. + * @param {string | string[]} newColumns + */ + addUserInfoColumns(newColumns) { + if (typeof newColumns === "string") newColumns = [newColumns] + newColumns = newColumns.filter(col => !this.userInfosColumns.includes(col)) + + this.userInfosColumns = this.userInfosColumns.concat(newColumns) + newColumns.forEach(col => { + this.userInfos.forEach(row => row[col] = null) + }) } /** @@ -27,26 +59,40 @@ class DataEntity { * @param {number} value Value to change to. * @returns */ - setResultValue(rowNr, questionNr, value) { + setResultValue(rowNr, questionNr, value) { + if (questionNr <= 0 || this.numOfQuestions < questionNr) { + throw new InvalidDataInputError(`Error at row ${rowNr}. Question number should be bewteen + 1 and ${this.numOfQuestions}. Your question number was ${questionNr}.`) + } if (value < this.type.minValue || this.type.maxValue < value) { - throw new InvalidDataInputError(`Error at row ${rowNr}. Input value should be between - ${this.type.minValue} and ${this.type.maxValue}. Your input value was ${value}.`) + throw new InvalidDataInputError(`Error at row ${rowNr}. Input value should be between + ${this.type.minValue} and ${this.type.maxValue}. Your input value was ${value}.`) } - if (questionNr <= 0 || this.type.numOfQuestions < questionNr) { - throw new InvalidDataInputError(`Error at row ${rowNr}. Question number should be bewteen - 1 and ${this.type.numOfQuestions}. Your question number was ${questionNr}.`) + const targetColumn = `Q${questionNr}` + this.results[rowNr][targetColumn] = value + } + + addEmptyRows(numberOfRows=1) { + for (let i = 0; i < numberOfRows; i++) { + this.userInfos.push(generateEmptyRow(this.userInfosColumns)) + this.results.push(generateEmptyRow(this.resultsColumns)) } - this.results[rowNr][`Q${questionNr}`] = value } insertEmptyRow(index) { - this.userInfos.splice(index, 0, {}) - this.results.splice(index, 0, {}) + const newUserInfoRow = generateEmptyRow(this.userInfosColumns) + const newResultRow = generateEmptyRow(this.resultsColumns) + this.userInfos.splice(index, 0, newUserInfoRow) + this.results.splice(index, 0, newResultRow) } - getSize() { + get size() { return this.userInfos.length } + + get numOfQuestions() { + return this.resultsColumns.length + } /** diff --git a/src/exceptions/DataExceptions.js b/src/exceptions/DataExceptions.js index 9037702..54ae099 100644 --- a/src/exceptions/DataExceptions.js +++ b/src/exceptions/DataExceptions.js @@ -5,7 +5,7 @@ class InvalidDataInputError extends Error { } } -class UndefinedEvaluationError extends Error { +class QuestionnaireTypeError extends Error { constructor(message) { super(message) this.name = this.constructor.name @@ -14,5 +14,5 @@ class UndefinedEvaluationError extends Error { export { InvalidDataInputError, - UndefinedEvaluationError + QuestionnaireTypeError } \ No newline at end of file diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 1b62fd6..49cc1bf 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -5,17 +5,27 @@ import { average } from "../utils/MathUtils" class DataAnalysisService { /** - * Calculate average score of a DataEntity according to its questionnaire type. + * Calculate an array of score of a DataEntity according to its questionnaire type. * @param {DataEntity} dataEntity */ - calculateScore(dataEntity) { + calculateScores(dataEntity) { const calculator = dataEntity.type.scoreCalculator - const scores = dataEntity.results.map(calculator) - return average(scores) + return dataEntity.results.map(calculator) + } + + + /** + * Calculate average score of a DataEntity according to its questionnaire type. + * @param {DataEntity} dataEntity + */ + calculateAverageScore(dataEntity) { + return average(this.calculateScores(dataEntity)) } + } -const dataAnalysisService = new DataAnalysisService() +const dataAnalysisService = new DataAnalysisService() + export default dataAnalysisService \ No newline at end of file diff --git a/src/services/DataService.js b/src/services/DataService.js index 9cc2798..ddc36cd 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -2,6 +2,7 @@ import { csvParse } from "d3" import { readFileSync } from "fs" import { DataEntity } from "../entities/DataEntity" import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" +import { infoResultSplit } from "../utils/DataUtils" class DataService { @@ -15,37 +16,6 @@ class DataService { } -/** - * Splitting raw questionnaire data object into user info object and result object. - * @param {object} rawData - * @returns {[object, object]} - */ -function infoResultSplit(rawData) { - const isInfoColumn = rawData.columns.map((value) => value.match(/Q[1-9][0-9]*/g)) - - const userInfos = [] - const results = [] - - rawData.forEach((row) => { - const userInfoRow = {} - const resultRow = {} - - rawData.columns.forEach((value, index) => { - if (isInfoColumn[index]) { - userInfoRow[value] = row[value] - } - else { - resultRow[value] = row[value] - } - }) - userInfos.push(userInfoRow) - results.push(resultRow) - }) - - return [userInfos, results] -} - - const dataService = new DataService() diff --git a/src/utils/DataUtils.js b/src/utils/DataUtils.js new file mode 100644 index 0000000..17f6950 --- /dev/null +++ b/src/utils/DataUtils.js @@ -0,0 +1,73 @@ +import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" + +function isQuestionColumn(columnName) { + return columnName.match(/Q[1-9][0-9]*/g) +} + + +/** + * Splitting raw questionnaire data object into user info object and result object. + * @param {array} rawData + * @returns {[object, object]} + */ +function infoResultSplit(rawData) { + const columns = Object.keys(rawData[0]) + const resultCols = columns.filter(col => isQuestionColumn(col)) + const userInfoCols = columns.filter(col => !isQuestionColumn(col)) + + const userInfos = [] + userInfos.columns = userInfoCols + const results = [] + results.columns = resultCols + + rawData.forEach((row) => { + const userInfoRow = {} + const resultRow = {} + + userInfoCols.forEach(col => userInfoRow[col] = row[col]) + resultCols.forEach(col => resultRow[col] = row[col]) + + userInfos.push(userInfoRow) + results.push(resultRow) + }) + + return [userInfos, results] +} + + +/** + * Given a QUESTIONNAIRE_TYPE, produce an array of `"Q1"`, ..., `"Qn"`, where `n` is the + * number of questions of that questionnaire type. + * @param {object} type + * @returns + */ +function generateResultColumns(type) { + if (type === QUESTIONNAIRE_TYPE.NONE) return [] + const columns = Array(type.numOfQuestions) + for (let i = 0; i < type.numOfQuestions; i++) { + columns[i] = i + 1 + } + return columns.map(i => `Q${i}`) +} + + +/** + * Returns an object with keys from given columns and null values. + * For example, input `["username", "age"]` would produce `{ username: null, age: null }`. + * @param {string[]} columns + * @returns + */ +function generateEmptyRow(columns) { + const newRow = {} + columns.forEach(col => { + newRow[col] = null + }) + return newRow +} + + +export { + infoResultSplit, + generateResultColumns, + generateEmptyRow +} \ No newline at end of file diff --git a/tests/TestUtils.js b/tests/TestUtils.js new file mode 100644 index 0000000..91e0234 --- /dev/null +++ b/tests/TestUtils.js @@ -0,0 +1,12 @@ +function arraysEqual(arr1, arr2) { + if (arr1.length !== arr2.length) return false + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) return false + } + return true +} + + +export { + arraysEqual +} \ No newline at end of file diff --git a/tests/entities/DataEntity.test.js b/tests/entities/DataEntity.test.js new file mode 100644 index 0000000..fbc3f01 --- /dev/null +++ b/tests/entities/DataEntity.test.js @@ -0,0 +1,83 @@ +import { test, expect, assert } from "vitest" +import { DataEntity } from "../../src/entities/DataEntity" +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" +import { InvalidDataInputError, QuestionnaireTypeError } from "../../src/exceptions/DataExceptions" +import { arraysEqual } from "../TestUtils" + + +const noneData = new DataEntity() +const susData = new DataEntity(QUESTIONNAIRE_TYPE.SUS) + +test( + "New questions can be added to NONE-type DataEntity", + () => { + noneData.addQuestions() + assert(arraysEqual(noneData.resultsColumns, ["Q1"])) + noneData.addQuestions(2) + assert(arraysEqual(noneData.resultsColumns, ["Q1", "Q2", "Q3"])) + } +) + +test( + "New user info rows can be added to DataEntity.", + () => { + noneData.addUserInfoColumns("id") + assert(arraysEqual(noneData.userInfosColumns, ["id"])) + noneData.addUserInfoColumns(["id", "age", "gender"]) + assert(arraysEqual(noneData.userInfosColumns, ["id", "age", "gender"])) + } +) + +test( + "New rows can be added to DataEntity.", + () => { + assert(noneData.size === 0) + noneData.addEmptyRows(2) + assert(noneData.size === 2) + } +) + +test( + "Fill empty locations with `null` when new columns are added.", + () => { + noneData.addQuestions() + assert(noneData.results[0]["Q4"] === null) + assert(noneData.results[1]["Q4"] === null) + + noneData.addUserInfoColumns("education") + assert(noneData.userInfos[0]["education"] === null) + assert(noneData.userInfos[1]["education"] === null) + } +) + +test( + "Cannot add questions to non-NONE-type DataEntity.", + () => { + expect(() => susData.addQuestions()) + .toThrow(QuestionnaireTypeError) + } +) + +test( + "DataEntity throws exception when setting result values is outside of value range.", + () => { + expect(() => susData.setResultValue(0, 0, 6)) + .toThrow(InvalidDataInputError) + expect(() => susData.setResultValue(0, 0, 0)) + .toThrow(InvalidDataInputError) + } +) + +test( + "DataEntity throws exception when question number is outside of range.", + () => { + expect(() => noneData.setResultValue(0, 10000, 5)) + .toThrow(InvalidDataInputError) + expect(() => susData.setResultValue(0, 0, 5)) + .toThrow(InvalidDataInputError) + expect(() => susData.setResultValue(0, 11, 5)) + .toThrow(InvalidDataInputError) + expect(() => susData.setResultValue(0, 0, 5)) + .toThrow(InvalidDataInputError) + } +) \ No newline at end of file diff --git a/tests/entities/DataEntityService.test.js b/tests/entities/DataEntityService.test.js deleted file mode 100644 index 8f07c3e..0000000 --- a/tests/entities/DataEntityService.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import { test, expect } from "vitest" -import { DataEntity } from "../../src/entities/DataEntity" -import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" -import { InvalidDataInputError } from "../../src/exceptions/DataExceptions" - - -const susDataEntity = new DataEntity(QUESTIONNAIRE_TYPE.SUS) -susDataEntity.insertEmptyRow(0) - -test( - "DataEntity throws exception when setting result values is outside of value range.", - () => { - expect(() => susDataEntity.setResultValue(0, 0, 6)) - .toThrow(InvalidDataInputError) - expect(() => susDataEntity.setResultValue(0, 0, 0)) - .toThrow(InvalidDataInputError) - } -) - -test( - "DataEntity throws exception when question number is outside of range.", - () => { - expect(() => susDataEntity.setResultValue(0, 11, 5)) - .toThrow(InvalidDataInputError) - expect(() => susDataEntity.setResultValue(0, 0, 5)) - .toThrow(InvalidDataInputError) - } -) \ No newline at end of file diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js index 798dc8c..3a5889b 100644 --- a/tests/services/DataAnalysisService.test.js +++ b/tests/services/DataAnalysisService.test.js @@ -2,7 +2,7 @@ import { test, expect, assert } from "vitest"; import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType"; import { DataEntity } from "../../src/entities/DataEntity"; import dataAnalysisService from "../../src/services/DataAnalysisService"; -import { UndefinedEvaluationError } from "../../src/exceptions/DataExceptions"; +import { QuestionnaireTypeError } from "../../src/exceptions/DataExceptions"; test( @@ -12,8 +12,8 @@ test( QUESTIONNAIRE_TYPE.NONE, [[5,5,5,5,5,5,5,5,5,5,5,5,5]] ) - expect(() => dataAnalysisService.calculateScore(testDataEntity)) - .toThrow(UndefinedEvaluationError) + expect(() => dataAnalysisService.calculateAverageScore(testDataEntity)) + .toThrow(QuestionnaireTypeError) } ) @@ -28,7 +28,7 @@ test( [5,5,5,5,5,5,5,5,5,5] ] ) - const susScore = dataAnalysisService.calculateScore(testDataEntity) + const susScore = dataAnalysisService.calculateAverageScore(testDataEntity) assert(susScore === 50, "Test SUS data should have SUS score 50.") } ) diff --git a/tests/services/DataService.test.js b/tests/services/DataService.test.js index cbe65e7..dbf5bed 100644 --- a/tests/services/DataService.test.js +++ b/tests/services/DataService.test.js @@ -1,5 +1,6 @@ import { test, assert } from "vitest" import dataService from "../../src/services/DataService" +import { arraysEqual } from "../TestUtils" test( "DataService imports csv correctly,", @@ -7,5 +8,20 @@ test( const data = dataService.importData("./tests/test-data/SUS-example.csv") assert(data.results.length === 40) assert(data.userInfos.length === 40) + + const importedUserInfoCols = data.userInfos.columns + const correctUserInfoCols = ["id", "age", "gender"] + assert( + arraysEqual(importedUserInfoCols, correctUserInfoCols), + "User Info columns imported correctly." + ) + + const importedResultCols = data.results.columns + const correctResultCols = ["Q1","Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10"] + assert( + arraysEqual(importedResultCols, correctResultCols), + "Result columns imported correctly." + ) + } ) \ No newline at end of file From 352b09c80c0ed27d6cb1080caa81c8268740b0ad Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sat, 14 Sep 2024 16:59:24 +0800 Subject: [PATCH 12/65] Add data export function --- src/services/DataService.js | 32 ++++++++++++++++++++-- tests/TestUtils.js | 30 +++++++++++++++++++- tests/services/DataAnalysisService.test.js | 27 +----------------- tests/services/DataService.test.js | 19 ++++++++++--- tests/test-data/SUS-example.csv | 2 +- 5 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/services/DataService.js b/src/services/DataService.js index ddc36cd..a0c724f 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -1,5 +1,5 @@ import { csvParse } from "d3" -import { readFileSync } from "fs" +import { readFileSync, write, writeFileSync } from "fs" import { DataEntity } from "../entities/DataEntity" import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { infoResultSplit } from "../utils/DataUtils" @@ -7,11 +7,37 @@ import { infoResultSplit } from "../utils/DataUtils" class DataService { - importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { + importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { const rawData = csvParse(readFileSync(filePath).toString()) const [userInfos, results] = infoResultSplit(rawData) return new DataEntity(type, userInfos, results) - } + } + + exportData(filePath, dataEntity) { + writeFileSync(filePath, this.stringify(dataEntity)) + } + + /** + * Return a string of CSV format representing the data entity. + * @param {DataEntity} dataEntity + */ + stringify(dataEntity) { + const headers = dataEntity.userInfosColumns + .concat(dataEntity.resultsColumns) + .toString() + + const lines = Array(dataEntity.size) + for (let row = 0; row < dataEntity.size; row++) { + let userInfoLine = Object.values(dataEntity.userInfos[row]).toString() + let resultLine = Object.values(dataEntity.results[row]).toString() + + if (!resultLine) lines[row] = userInfoLine + else if (!userInfoLine) lines[row] = resultLine + else lines[row] = `${userInfoLine},${resultLine}` + } + + return `${headers}\r\n${lines.join("\r\n")}` + } } diff --git a/tests/TestUtils.js b/tests/TestUtils.js index 91e0234..c649c4d 100644 --- a/tests/TestUtils.js +++ b/tests/TestUtils.js @@ -1,3 +1,5 @@ +import { DataEntity } from "../src/entities/DataEntity" + function arraysEqual(arr1, arr2) { if (arr1.length !== arr2.length) return false for (let i = 0; i < arr1.length; i++) { @@ -6,7 +8,33 @@ function arraysEqual(arr1, arr2) { return true } +/** + * A helper function for generating DataEntity. + * @param {object} type Type of questionnaire. + * @param {number[][]} valueMatrix An matrix of result values. + */ +function generateDataEntity(type, valueMatrix) { + const userInfos = [] + const results = [] + + valueMatrix.forEach((arr) => { + let result = {} + arr.forEach((value, index) => { + result[`Q${index + 1}`] = value + }) + + userInfos.push({}) + results.push(result) + }) + + return new DataEntity( + type, + userInfos, + results + ) +} export { - arraysEqual + arraysEqual, + generateDataEntity } \ No newline at end of file diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js index 3a5889b..22fb86b 100644 --- a/tests/services/DataAnalysisService.test.js +++ b/tests/services/DataAnalysisService.test.js @@ -1,8 +1,8 @@ import { test, expect, assert } from "vitest"; import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType"; -import { DataEntity } from "../../src/entities/DataEntity"; import dataAnalysisService from "../../src/services/DataAnalysisService"; import { QuestionnaireTypeError } from "../../src/exceptions/DataExceptions"; +import { generateDataEntity } from "../TestUtils"; test( @@ -34,28 +34,3 @@ test( ) -/** - * A helper function for generating DataEntity. - * @param {object} type Type of questionnaire. - * @param {number[][]} valueMatrix An matrix of result values. - */ -function generateDataEntity(type, valueMatrix) { - const userInfos = [] - const results = [] - - valueMatrix.forEach((arr) => { - let result = {} - arr.forEach((value, index) => { - result[`Q${index + 1}`] = value - }) - - userInfos.push({}) - results.push(result) - }) - - return new DataEntity( - type, - userInfos, - results - ) -} \ No newline at end of file diff --git a/tests/services/DataService.test.js b/tests/services/DataService.test.js index dbf5bed..7a5a2c7 100644 --- a/tests/services/DataService.test.js +++ b/tests/services/DataService.test.js @@ -1,27 +1,38 @@ import { test, assert } from "vitest" +import { readFileSync } from "fs" import dataService from "../../src/services/DataService" import { arraysEqual } from "../TestUtils" + +const data = dataService.importData("./tests/test-data/SUS-example.csv") + test( - "DataService imports csv correctly,", + "DataService imports CSV correctly.", () => { - const data = dataService.importData("./tests/test-data/SUS-example.csv") assert(data.results.length === 40) assert(data.userInfos.length === 40) - const importedUserInfoCols = data.userInfos.columns + const importedUserInfoCols = data.userInfosColumns const correctUserInfoCols = ["id", "age", "gender"] assert( arraysEqual(importedUserInfoCols, correctUserInfoCols), "User Info columns imported correctly." ) - const importedResultCols = data.results.columns + const importedResultCols = data.resultsColumns const correctResultCols = ["Q1","Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10"] assert( arraysEqual(importedResultCols, correctResultCols), "Result columns imported correctly." ) + } +) +test( + "DataService stringifies DataEntity correctly.", + () => { + const correctCsv = readFileSync("./tests/test-data/SUS-example.csv").toString() + const stringifiedData = dataService.stringify(data) + assert(stringifiedData === correctCsv) } ) \ No newline at end of file diff --git a/tests/test-data/SUS-example.csv b/tests/test-data/SUS-example.csv index fa0efc0..230984d 100644 --- a/tests/test-data/SUS-example.csv +++ b/tests/test-data/SUS-example.csv @@ -38,4 +38,4 @@ id,age,gender,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10 37,37,F,4,2,3,3,3,2,4,3,4,4 38,30,F,4,2,3,3,5,2,5,3,5,2 39,22,F,5,3,5,2,3,2,4,1,3,2 -40,26,M,5,2,5,1,5,2,4,1,3,1 +40,26,M,5,2,5,1,5,2,4,1,3,1 \ No newline at end of file From e882eb0adca001fcca99033d27058eb610cb7124 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 16 Sep 2024 18:30:16 +0800 Subject: [PATCH 13/65] Complete DataEntity --- src/entities/DataEntity.js | 177 ++++++++++++++++++++++++------ src/services/DataService.js | 4 +- src/utils/DataUtils.js | 1 + tests/TestUtils.js | 2 +- tests/entities/DataEntity.test.js | 16 ++- 5 files changed, 161 insertions(+), 39 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 7bc027b..9c5df09 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,6 +1,6 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { InvalidDataInputError, QuestionnaireTypeError } from "../exceptions/DataExceptions" -import { generateEmptyRow, generateResultColumns } from "../utils/DataUtils" +import { generateEmptyRow, generateResultColumns, isQuestionColumn } from "../utils/DataUtils" /** @@ -12,26 +12,38 @@ class DataEntity { /** * Constructor * @param {object} type Type of questionnaire. `DATA_TYPE.NONE` by default. - * @param {UserInfo[]} userInfos A list of users' information. `null` by default. - * @param {object[]} results A list of questionnaire results. `null` by default. + * @param {UserInfo[]} userInfos A list of users' information. + * @param {object[]} results A list of questionnaire results. */ constructor(type=QUESTIONNAIRE_TYPE.NONE, userInfos=[], results=[]) { this.type = type this.userInfos = userInfos this.results = results - if (!this.userInfos || userInfos.length === 0) this.userInfosColumns = [] - else this.userInfosColumns = Object.keys(userInfos[0]) + if (!this.userInfos || userInfos.length === 0) { this.userInfosColumns = [] } + else { this.userInfosColumns = Object.keys(userInfos[0]) } - if (!this.results || this.results.length === 0) this.resultsColumns = generateResultColumns(type) - else this.resultsColumns = Object.keys(results[0]) + if (!this.results || results.length === 0) { + this.resultsColumns = generateResultColumns(type) + } + else { this.resultsColumns = Object.keys(results[0]) } } - addQuestions(numOfNewQuestions=1) { + + + /** + * Column operations + */ + + /** + * Add given number of new questions to the questionnaire. + * @param {number} numOfQuestions + */ + addQuestions(numOfQuestions=1) { if (this.type !== QUESTIONNAIRE_TYPE.NONE) { throw new QuestionnaireTypeError("Only NONE-type data allows adding new questions.") } - for (let i = 0; i < numOfNewQuestions; i++) { + for (let i = 0; i < numOfQuestions; i++) { const newCol = `Q${this.numOfQuestions + 1}` this.resultsColumns.push(newCol) this.results.forEach(row => row[newCol] = null) @@ -39,19 +51,88 @@ class DataEntity { } /** - * Given a string or an array of string, add new strings among them as user info columns. - * @param {string | string[]} newColumns + * Delete given number of new questions to the questionnaire. + * @param {number} numOfQuestions + */ + deleteQuestions(numOfQuestions=1) { + if (this.type !== QUESTIONNAIRE_TYPE.NONE) { + throw new QuestionnaireTypeError("Only NONE-type data allows deleting questions.") + } + for (let i = 0; i < numOfQuestions; i++) { + const deletedCol = this.resultsColumns.pop() + this.results.forEach(row => delete row[deletedCol]) + } + } + + setNumOfQUestions(numOfQuestions) { + if (this.type !== QUESTIONNAIRE_TYPE.NONE) { + throw new QuestionnaireTypeError("Only NONE-type data allows setting number of questions.") + } + const difference = numOfQuestions - this.numOfQuestions + if (difference === 0) { return } + if (difference >= 0) { this.addQuestions(difference) } + else { this.deleteQuestions(difference) } + } + + /** + * Given a string or an array of strings, add new strings among them as user info columns. + * @param {string | string[]} columns */ - addUserInfoColumns(newColumns) { - if (typeof newColumns === "string") newColumns = [newColumns] - newColumns = newColumns.filter(col => !this.userInfosColumns.includes(col)) + addUserInfoColumns(columns) { + if (typeof columns === "string") { columns = [columns] } + columns = columns.filter(col => !this.userInfosColumns.includes(col)) - this.userInfosColumns = this.userInfosColumns.concat(newColumns) - newColumns.forEach(col => { + this.userInfosColumns = this.userInfosColumns.concat(columns) + columns.forEach(col => { this.userInfos.forEach(row => row[col] = null) }) } + /** + * Given a string or an array of strings, delete user info columns of those names. + * @param {string | string[]} columns + */ + deleteUserInfoColumns(columns) { + if (typeof columns === "string") { columns = [columns] } + + this.userInfosColumns = this.userInfosColumns.filter(col => !columns.includes(col)) + columns.forEach(col => { + this.userInfos.forEach(row => delete row[col]) + }) + } + + + /** + * Row operations + */ + + /** + * Append given number of empty rows to the data. + * @param {number} numberOfRows + */ + addEmptyRows(numberOfRows=1) { + for (let i = 0; i < numberOfRows; i++) { + this.userInfos.push(generateEmptyRow(this.userInfosColumns)) + this.results.push(generateEmptyRow(this.resultsColumns)) + } + } + + /** + * In sert 1 empty row at the given index. + * @param {number} index + */ + insertEmptyRow(index) { + const newUserInfoRow = generateEmptyRow(this.userInfosColumns) + const newResultRow = generateEmptyRow(this.resultsColumns) + this.userInfos.splice(index, 0, newUserInfoRow) + this.results.splice(index, 0, newResultRow) + } + + + /** + * Data manipulation + */ + /** * Set questionnaire result value. * @param {number} rowNr Target row. @@ -72,20 +153,39 @@ class DataEntity { this.results[rowNr][targetColumn] = value } - addEmptyRows(numberOfRows=1) { - for (let i = 0; i < numberOfRows; i++) { - this.userInfos.push(generateEmptyRow(this.userInfosColumns)) - this.results.push(generateEmptyRow(this.resultsColumns)) + /** + * Set user info value. + * @param {number} rowNr + * @param {string} column + * @param {number} value + */ + setUserInfoValue(rowNr, column, value) { + if (!this.userInfosColumns.includes(column)) { + throw new InvalidDataInputError(`User info column "${column}" does not exist. Valid columns + are [${this.userInfosColumns}]`) } + this.userInfos[rowNr][column] = value } - insertEmptyRow(index) { - const newUserInfoRow = generateEmptyRow(this.userInfosColumns) - const newResultRow = generateEmptyRow(this.resultsColumns) - this.userInfos.splice(index, 0, newUserInfoRow) - this.results.splice(index, 0, newResultRow) + /** + * Set value at given column and row number. + * @param {number} rowNr + * @param {string} column + * @param {number} value + */ + setValue(rowNr, column, value) { + if (isQuestionColumn(column)) { + const questionNr = parseInt(column.substring(1)) + this.setResultValue(rowNr, questionNr, value) + } + else { this.setUserInfoValue(rowNr, column, value) } } + + /** + * Getters + */ + get size() { return this.userInfos.length } @@ -93,18 +193,25 @@ class DataEntity { get numOfQuestions() { return this.resultsColumns.length } - - /** - * Check if the data is valid according to the type. - * @return {boolean} - */ - isValid() { - return ( - this.userInfos.length === this.results.length - ) + get columns() { + return this.userInfosColumns.concat(this.resultsColumns) } + row(rowNumber) { + return { + userInfo: this.userInfos[rowNumber], + result: this.results[rowNumber] + } + } + + loc(rowNr, column) { + if (isQuestionColumn(column)) { + return this.results[rowNr][column] + } + return this.userInfos[rowNr][column] + } + } @@ -124,4 +231,4 @@ class DataEntity { // } -export { DataEntity }; +export default DataEntity; diff --git a/src/services/DataService.js b/src/services/DataService.js index a0c724f..8a92ddf 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -1,6 +1,6 @@ import { csvParse } from "d3" -import { readFileSync, write, writeFileSync } from "fs" -import { DataEntity } from "../entities/DataEntity" +import { readFileSync, writeFileSync } from "fs" +import DataEntity from "../entities/DataEntity" import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { infoResultSplit } from "../utils/DataUtils" diff --git a/src/utils/DataUtils.js b/src/utils/DataUtils.js index 17f6950..636d499 100644 --- a/src/utils/DataUtils.js +++ b/src/utils/DataUtils.js @@ -67,6 +67,7 @@ function generateEmptyRow(columns) { export { + isQuestionColumn, infoResultSplit, generateResultColumns, generateEmptyRow diff --git a/tests/TestUtils.js b/tests/TestUtils.js index c649c4d..14e096f 100644 --- a/tests/TestUtils.js +++ b/tests/TestUtils.js @@ -1,4 +1,4 @@ -import { DataEntity } from "../src/entities/DataEntity" +import DataEntity from "../src/entities/DataEntity" function arraysEqual(arr1, arr2) { if (arr1.length !== arr2.length) return false diff --git a/tests/entities/DataEntity.test.js b/tests/entities/DataEntity.test.js index fbc3f01..b22f26a 100644 --- a/tests/entities/DataEntity.test.js +++ b/tests/entities/DataEntity.test.js @@ -1,5 +1,5 @@ import { test, expect, assert } from "vitest" -import { DataEntity } from "../../src/entities/DataEntity" +import DataEntity from "../../src/entities/DataEntity" import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" import { InvalidDataInputError, QuestionnaireTypeError } from "../../src/exceptions/DataExceptions" import { arraysEqual } from "../TestUtils" @@ -37,6 +37,20 @@ test( } ) +test( + "Getter and setter working correctly.", + () => { + noneData.setValue(0, "Q1", 5) + noneData.setValue(0, "age", 50) + assert(noneData.loc(0, "Q1") === 5) + assert(noneData.loc(0, "age") === 50) + expect(() => noneData.setValue(0, "Q100", 1)) + .toThrow(InvalidDataInputError) + expect(() => noneData.setValue(0, "Age", 1)) + .toThrow(InvalidDataInputError) + } +) + test( "Fill empty locations with `null` when new columns are added.", () => { From 36456388a1a0887e9260b0a326dd9e31be79e3fa Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Tue, 17 Sep 2024 13:35:43 +0800 Subject: [PATCH 14/65] Add DiagramEntity --- src/constants/DiagramType.js | 22 ++++++++++++++++++++++ src/entities/DataEntity.js | 23 ++++++++++++----------- src/entities/DiagramEntity.js | 16 +++++++++++++++- src/utils/DiagramUtils.js | 0 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 src/constants/DiagramType.js create mode 100644 src/utils/DiagramUtils.js diff --git a/src/constants/DiagramType.js b/src/constants/DiagramType.js new file mode 100644 index 0000000..deb40e4 --- /dev/null +++ b/src/constants/DiagramType.js @@ -0,0 +1,22 @@ + + +const DIAGRAM_TYPE = {} + +DIAGRAM_TYPE.HIST = { + name: "Histogram", + requiredConfig: ["x", "xmin", "xmax", "ymin", "ymax", "bin"], + optionalConfig: [] +} + + +DIAGRAM_TYPE.SCATTER = { + name: "Scatter", + requiredConfig: ["x", "y", "xmin", "xmax", "ymin", "ymax"], + optionalConfig: [] +} + + + +Object.freeze(DIAGRAM_TYPE) + +export default DIAGRAM_TYPE \ No newline at end of file diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 9c5df09..f2777ca 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -7,7 +7,7 @@ import { generateEmptyRow, generateResultColumns, isQuestionColumn } from "../ut * A class for representing a series of questionnaire data. * */ -class DataEntity { +export default class DataEntity { /** * Constructor @@ -31,9 +31,9 @@ class DataEntity { - /** + /************************* * Column operations - */ + *************************/ /** * Add given number of new questions to the questionnaire. @@ -102,9 +102,11 @@ class DataEntity { } + /** + /************************* * Row operations - */ + *************************/ /** * Append given number of empty rows to the data. @@ -129,9 +131,10 @@ class DataEntity { } - /** + + /************************* * Data manipulation - */ + *************************/ /** * Set questionnaire result value. @@ -182,9 +185,10 @@ class DataEntity { } - /** + + /************************* * Getters - */ + *************************/ get size() { return this.userInfos.length @@ -229,6 +233,3 @@ class DataEntity { // } // } - - -export default DataEntity; diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index 9fc33b6..bd94f22 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -1,3 +1,17 @@ +import DataEntity from "./DataEntity"; + export default class DiagramEntity { - + + /** + * @param {object} type + * @param {DataEntity} dataEntity + */ + constructor(type, dataEntity, config={}) { + this.type = type + this.linkedData = dataEntity + this.config = config + } + + + } \ No newline at end of file diff --git a/src/utils/DiagramUtils.js b/src/utils/DiagramUtils.js new file mode 100644 index 0000000..e69de29 From dd4a222044c164d3ed6e6e241fe62029fbc7e1ec Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Tue, 17 Sep 2024 14:19:15 +0800 Subject: [PATCH 15/65] Refactor DataEntity --- src/entities/DataEntity.js | 81 +++++++++++------------------ src/services/DataAnalysisService.js | 2 +- src/services/DataService.js | 22 ++------ tests/TestUtils.js | 15 +++--- tests/entities/DataEntity.test.js | 18 +++---- tests/services/DataService.test.js | 7 ++- 6 files changed, 54 insertions(+), 91 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index f2777ca..f0bc388 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -15,18 +15,14 @@ export default class DataEntity { * @param {UserInfo[]} userInfos A list of users' information. * @param {object[]} results A list of questionnaire results. */ - constructor(type=QUESTIONNAIRE_TYPE.NONE, userInfos=[], results=[]) { + constructor(type=QUESTIONNAIRE_TYPE.NONE, data=[]) { this.type = type - this.userInfos = userInfos - this.results = results + this.data = data + this.columns = { userInfo: [], questions: [] } - if (!this.userInfos || userInfos.length === 0) { this.userInfosColumns = [] } - else { this.userInfosColumns = Object.keys(userInfos[0]) } - - if (!this.results || results.length === 0) { - this.resultsColumns = generateResultColumns(type) - } - else { this.resultsColumns = Object.keys(results[0]) } + if (data.length === 0) { return } + this.columns.questions = Object.keys(data[0]).filter(isQuestionColumn) + this.columns.userInfo = Object.keys(data[0]).filter(k => !isQuestionColumn(k)) } @@ -45,8 +41,8 @@ export default class DataEntity { } for (let i = 0; i < numOfQuestions; i++) { const newCol = `Q${this.numOfQuestions + 1}` - this.resultsColumns.push(newCol) - this.results.forEach(row => row[newCol] = null) + this.columns.questions.push(newCol) + this.data.forEach(row => row[newCol] = null) } } @@ -59,8 +55,8 @@ export default class DataEntity { throw new QuestionnaireTypeError("Only NONE-type data allows deleting questions.") } for (let i = 0; i < numOfQuestions; i++) { - const deletedCol = this.resultsColumns.pop() - this.results.forEach(row => delete row[deletedCol]) + const deletedCol = this.columns.questions.pop() + this.data.forEach(row => delete row[deletedCol]) } } @@ -80,11 +76,11 @@ export default class DataEntity { */ addUserInfoColumns(columns) { if (typeof columns === "string") { columns = [columns] } - columns = columns.filter(col => !this.userInfosColumns.includes(col)) + columns = columns.filter(col => !this.userInfoColumns.includes(col)) - this.userInfosColumns = this.userInfosColumns.concat(columns) + this.columns.userInfo = this.columns.userInfo.concat(columns) columns.forEach(col => { - this.userInfos.forEach(row => row[col] = null) + this.data.forEach(row => row[col] = null) }) } @@ -95,9 +91,9 @@ export default class DataEntity { deleteUserInfoColumns(columns) { if (typeof columns === "string") { columns = [columns] } - this.userInfosColumns = this.userInfosColumns.filter(col => !columns.includes(col)) + this.userInfoColumns = this.userInfoColumns.filter(col => !columns.includes(col)) columns.forEach(col => { - this.userInfos.forEach(row => delete row[col]) + this.data.forEach(row => delete row[col]) }) } @@ -114,8 +110,7 @@ export default class DataEntity { */ addEmptyRows(numberOfRows=1) { for (let i = 0; i < numberOfRows; i++) { - this.userInfos.push(generateEmptyRow(this.userInfosColumns)) - this.results.push(generateEmptyRow(this.resultsColumns)) + this.data.push(generateEmptyRow(this.allColumns)) } } @@ -124,10 +119,8 @@ export default class DataEntity { * @param {number} index */ insertEmptyRow(index) { - const newUserInfoRow = generateEmptyRow(this.userInfosColumns) - const newResultRow = generateEmptyRow(this.resultsColumns) - this.userInfos.splice(index, 0, newUserInfoRow) - this.results.splice(index, 0, newResultRow) + const newRow = generateEmptyRow(this.allColumns) + this.data.splice(index, 0, newRow) } @@ -153,7 +146,7 @@ export default class DataEntity { ${this.type.minValue} and ${this.type.maxValue}. Your input value was ${value}.`) } const targetColumn = `Q${questionNr}` - this.results[rowNr][targetColumn] = value + this.data[rowNr][targetColumn] = value } /** @@ -163,11 +156,11 @@ export default class DataEntity { * @param {number} value */ setUserInfoValue(rowNr, column, value) { - if (!this.userInfosColumns.includes(column)) { + if (!this.userInfoColumns.includes(column)) { throw new InvalidDataInputError(`User info column "${column}" does not exist. Valid columns - are [${this.userInfosColumns}]`) + are [${this.userInfoColumns}]`) } - this.userInfos[rowNr][column] = value + this.data[rowNr][column] = value } /** @@ -190,31 +183,19 @@ export default class DataEntity { * Getters *************************/ - get size() { - return this.userInfos.length - } + get size() { return this.data.length } - get numOfQuestions() { - return this.resultsColumns.length - } + get numOfQuestions() { return this.questionColumns.length } - get columns() { - return this.userInfosColumns.concat(this.resultsColumns) - } + get allColumns() {return this.userInfoColumns.concat(this.questionColumns)} - row(rowNumber) { - return { - userInfo: this.userInfos[rowNumber], - result: this.results[rowNumber] - } - } + get userInfoColumns() { return this.columns.userInfo} - loc(rowNr, column) { - if (isQuestionColumn(column)) { - return this.results[rowNr][column] - } - return this.userInfos[rowNr][column] - } + get questionColumns() { return this.columns.questions } + + row(rowNumber) { return this.data[rowNumber] } + + loc(rowNr, column) { return this.data[rowNr][column] } } diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 49cc1bf..94ddd16 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -10,7 +10,7 @@ class DataAnalysisService { */ calculateScores(dataEntity) { const calculator = dataEntity.type.scoreCalculator - return dataEntity.results.map(calculator) + return dataEntity.data.map(calculator) } diff --git a/src/services/DataService.js b/src/services/DataService.js index 8a92ddf..c0694bc 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -2,15 +2,13 @@ import { csvParse } from "d3" import { readFileSync, writeFileSync } from "fs" import DataEntity from "../entities/DataEntity" import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" -import { infoResultSplit } from "../utils/DataUtils" class DataService { importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { - const rawData = csvParse(readFileSync(filePath).toString()) - const [userInfos, results] = infoResultSplit(rawData) - return new DataEntity(type, userInfos, results) + const data = csvParse(readFileSync(filePath).toString()) + return new DataEntity(type, data) } exportData(filePath, dataEntity) { @@ -22,20 +20,8 @@ class DataService { * @param {DataEntity} dataEntity */ stringify(dataEntity) { - const headers = dataEntity.userInfosColumns - .concat(dataEntity.resultsColumns) - .toString() - - const lines = Array(dataEntity.size) - for (let row = 0; row < dataEntity.size; row++) { - let userInfoLine = Object.values(dataEntity.userInfos[row]).toString() - let resultLine = Object.values(dataEntity.results[row]).toString() - - if (!resultLine) lines[row] = userInfoLine - else if (!userInfoLine) lines[row] = resultLine - else lines[row] = `${userInfoLine},${resultLine}` - } - + const headers = dataEntity.allColumns.toString() + const lines = dataEntity.data.map(r => Object.values(r).toString()) return `${headers}\r\n${lines.join("\r\n")}` } diff --git a/tests/TestUtils.js b/tests/TestUtils.js index 14e096f..36e221e 100644 --- a/tests/TestUtils.js +++ b/tests/TestUtils.js @@ -11,26 +11,23 @@ function arraysEqual(arr1, arr2) { /** * A helper function for generating DataEntity. * @param {object} type Type of questionnaire. - * @param {number[][]} valueMatrix An matrix of result values. + * @param {number[][]} valueMatrix An matrix of questionnaire result values. */ function generateDataEntity(type, valueMatrix) { - const userInfos = [] - const results = [] + const data = [] valueMatrix.forEach((arr) => { - let result = {} + let row = {} arr.forEach((value, index) => { - result[`Q${index + 1}`] = value + row[`Q${index + 1}`] = value }) - userInfos.push({}) - results.push(result) + data.push(row) }) return new DataEntity( type, - userInfos, - results + data ) } diff --git a/tests/entities/DataEntity.test.js b/tests/entities/DataEntity.test.js index b22f26a..70c6320 100644 --- a/tests/entities/DataEntity.test.js +++ b/tests/entities/DataEntity.test.js @@ -12,19 +12,19 @@ test( "New questions can be added to NONE-type DataEntity", () => { noneData.addQuestions() - assert(arraysEqual(noneData.resultsColumns, ["Q1"])) + assert(arraysEqual(noneData.questionColumns, ["Q1"])) noneData.addQuestions(2) - assert(arraysEqual(noneData.resultsColumns, ["Q1", "Q2", "Q3"])) + assert(arraysEqual(noneData.questionColumns, ["Q1", "Q2", "Q3"])) } ) test( - "New user info rows can be added to DataEntity.", + "New user info columns can be added to DataEntity.", () => { noneData.addUserInfoColumns("id") - assert(arraysEqual(noneData.userInfosColumns, ["id"])) + assert(arraysEqual(noneData.userInfoColumns, ["id"])) noneData.addUserInfoColumns(["id", "age", "gender"]) - assert(arraysEqual(noneData.userInfosColumns, ["id", "age", "gender"])) + assert(arraysEqual(noneData.userInfoColumns, ["id", "age", "gender"])) } ) @@ -55,12 +55,12 @@ test( "Fill empty locations with `null` when new columns are added.", () => { noneData.addQuestions() - assert(noneData.results[0]["Q4"] === null) - assert(noneData.results[1]["Q4"] === null) + assert(noneData.loc(0, "Q4") === null) + assert(noneData.loc(1, "Q4") === null) noneData.addUserInfoColumns("education") - assert(noneData.userInfos[0]["education"] === null) - assert(noneData.userInfos[1]["education"] === null) + assert(noneData.loc(0, "education") === null) + assert(noneData.loc(1, "education") === null) } ) diff --git a/tests/services/DataService.test.js b/tests/services/DataService.test.js index 7a5a2c7..9f9c586 100644 --- a/tests/services/DataService.test.js +++ b/tests/services/DataService.test.js @@ -9,17 +9,16 @@ const data = dataService.importData("./tests/test-data/SUS-example.csv") test( "DataService imports CSV correctly.", () => { - assert(data.results.length === 40) - assert(data.userInfos.length === 40) + assert(data.size === 40) - const importedUserInfoCols = data.userInfosColumns + const importedUserInfoCols = data.userInfoColumns const correctUserInfoCols = ["id", "age", "gender"] assert( arraysEqual(importedUserInfoCols, correctUserInfoCols), "User Info columns imported correctly." ) - const importedResultCols = data.resultsColumns + const importedResultCols = data.questionColumns const correctResultCols = ["Q1","Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10"] assert( arraysEqual(importedResultCols, correctResultCols), From 0dfb8678d8989397b0c3ab8253ffb0176761a526 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Tue, 17 Sep 2024 21:43:09 +0800 Subject: [PATCH 16/65] Remove fs import --- src/entities/DataEntity.js | 4 ++++ src/services/DataService.js | 14 +++++++------- tests/TestUtils.js | 5 +---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index f0bc388..4d4f89d 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -177,6 +177,10 @@ export default class DataEntity { else { this.setUserInfoValue(rowNr, column, value) } } + setType(type) { + this.type = type + } + /************************* diff --git a/src/services/DataService.js b/src/services/DataService.js index c0694bc..6762d84 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -1,19 +1,19 @@ -import { csvParse } from "d3" -import { readFileSync, writeFileSync } from "fs" +import * as d3 from "d3" +// import { readFileSync, writeFileSync } from "fs" import DataEntity from "../entities/DataEntity" import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" class DataService { - importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { - const data = csvParse(readFileSync(filePath).toString()) + async importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { + const data = await d3.csv(filePath, d3.autoType) return new DataEntity(type, data) } - exportData(filePath, dataEntity) { - writeFileSync(filePath, this.stringify(dataEntity)) - } + // exportData(filePath, dataEntity) { + // writeFileSync(filePath, this.stringify(dataEntity)) + // } /** * Return a string of CSV format representing the data entity. diff --git a/tests/TestUtils.js b/tests/TestUtils.js index 36e221e..c1d6d4d 100644 --- a/tests/TestUtils.js +++ b/tests/TestUtils.js @@ -25,10 +25,7 @@ function generateDataEntity(type, valueMatrix) { data.push(row) }) - return new DataEntity( - type, - data - ) + return new DataEntity(type, data) } export { From 4b944ecb39cd9b6a9cc25e97e180f711e784c5e9 Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:25:10 +0200 Subject: [PATCH 17/65] Update WorkspaceEntity.js test commit --- src/entities/WorkspaceEntity.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js index 349ca28..ae4fe15 100644 --- a/src/entities/WorkspaceEntity.js +++ b/src/entities/WorkspaceEntity.js @@ -1,3 +1,5 @@ export default class WorkspaceEntity { + + // test pull } \ No newline at end of file From dfea1cea9a7cf73a1742f87f689fa3bee53ed6f9 Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:06:57 +0200 Subject: [PATCH 18/65] Update Visualization.jsx Sample Data Graph --- .../workspace/visualization/Visualization.jsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/app/pages/workspace/visualization/Visualization.jsx b/src/app/pages/workspace/visualization/Visualization.jsx index 2143b34..7444edc 100644 --- a/src/app/pages/workspace/visualization/Visualization.jsx +++ b/src/app/pages/workspace/visualization/Visualization.jsx @@ -4,8 +4,35 @@ * * @returns {ReactNode} Rendered component. */ + +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + +const data = [ + { name: 'sampleA', v1: 4000, v2: 2000, v3: 1000 }, + { name: 'sampleB', v1: 3000, v2: 1000, v3: 2000 }, + { name: 'sampleC', v1: 2000, v2: 7000, v3: 2090 }, +]; + export default function Visualization() { return ( -

Visualization

+ + + + + + + + + + + + ) } From f146b1884b10e021df20419f98d85c35bac8f736 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 19 Sep 2024 10:50:49 +0800 Subject: [PATCH 19/65] Fix bug in DataService --- src/services/DataService.js | 8 ++++++-- tests/services/DataService.test.js | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/services/DataService.js b/src/services/DataService.js index 6762d84..717ffd3 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -1,5 +1,4 @@ import * as d3 from "d3" -// import { readFileSync, writeFileSync } from "fs" import DataEntity from "../entities/DataEntity" import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" @@ -19,12 +18,17 @@ class DataService { * Return a string of CSV format representing the data entity. * @param {DataEntity} dataEntity */ - stringify(dataEntity) { + serialize(dataEntity) { const headers = dataEntity.allColumns.toString() const lines = dataEntity.data.map(r => Object.values(r).toString()) return `${headers}\r\n${lines.join("\r\n")}` } + deserialize(string, type=QUESTIONNAIRE_TYPE.NONE) { + const data = d3.csvParse(string) + return new DataEntity(type, data) + } + } diff --git a/tests/services/DataService.test.js b/tests/services/DataService.test.js index 9f9c586..34d80c9 100644 --- a/tests/services/DataService.test.js +++ b/tests/services/DataService.test.js @@ -2,9 +2,12 @@ import { test, assert } from "vitest" import { readFileSync } from "fs" import dataService from "../../src/services/DataService" import { arraysEqual } from "../TestUtils" +import { readFileSync } from "fs" +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" -const data = dataService.importData("./tests/test-data/SUS-example.csv") +const string = readFileSync("./tests/test-data/SUS-example.csv").toString() +const data = dataService.deserialize(string, QUESTIONNAIRE_TYPE.SUS) test( "DataService imports CSV correctly.", @@ -31,7 +34,7 @@ test( "DataService stringifies DataEntity correctly.", () => { const correctCsv = readFileSync("./tests/test-data/SUS-example.csv").toString() - const stringifiedData = dataService.stringify(data) + const stringifiedData = dataService.serialize(data) assert(stringifiedData === correctCsv) } ) \ No newline at end of file From f0cf40098db82d9398c582e5ca8a21332203b60c Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 19 Sep 2024 16:49:29 +0800 Subject: [PATCH 20/65] Add DiagramType --- package-lock.json | 76 +++++++++---------- package.json | 1 + src/components/Diagram.jsx | 14 ++++ src/constants/DiagramType.js | 63 +++++++++++++++- src/entities/DiagramEntity.js | 48 ++++++++++-- src/exceptions/DataExceptions.js | 10 ++- src/utils/DiagramUtils.js | 0 src/utils/Document.js | 126 +++++++++++++++++++++++++++++++ 8 files changed, 287 insertions(+), 51 deletions(-) delete mode 100644 src/utils/DiagramUtils.js create mode 100644 src/utils/Document.js diff --git a/package-lock.json b/package-lock.json index 07a5469..7c46479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@mui/material": "^5.16.6", + "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", "react-resizable-panels": "^2.0.23", @@ -1260,6 +1261,19 @@ "node": ">= 8" } }, + "node_modules/@observablehq/plot": { + "version": "0.6.16", + "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.16.tgz", + "integrity": "sha512-LRi9Rn93yUx90MIo2Md7+vazxO3Wiat14but2ttCER0xVS+jnfoUjuCGoz6H7bz/lgI9CFcW0HWlvWjMFjAv8g==", + "dependencies": { + "d3": "^7.9.0", + "interval-tree-1d": "^1.0.0", + "isoformat": "^0.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1958,6 +1972,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/binary-search-bounds": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2119,7 +2138,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, "engines": { "node": ">= 10" } @@ -2174,7 +2192,6 @@ "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "dev": true, "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -2215,7 +2232,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dev": true, "dependencies": { "internmap": "1 - 2" }, @@ -2227,7 +2243,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "dev": true, "engines": { "node": ">=12" } @@ -2236,7 +2251,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dev": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -2252,7 +2266,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "dev": true, "dependencies": { "d3-path": "1 - 3" }, @@ -2264,7 +2277,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "dev": true, "engines": { "node": ">=12" } @@ -2273,7 +2285,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dev": true, "dependencies": { "d3-array": "^3.2.0" }, @@ -2285,7 +2296,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "dev": true, "dependencies": { "delaunator": "5" }, @@ -2297,7 +2307,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "dev": true, "engines": { "node": ">=12" } @@ -2306,7 +2315,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dev": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -2319,7 +2327,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "dev": true, "dependencies": { "commander": "7", "iconv-lite": "0.6", @@ -2344,7 +2351,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "dev": true, "engines": { "node": ">=12" } @@ -2353,7 +2359,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dev": true, "dependencies": { "d3-dsv": "1 - 3" }, @@ -2365,7 +2370,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dev": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -2379,7 +2383,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "dev": true, "engines": { "node": ">=12" } @@ -2388,7 +2391,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "dev": true, "dependencies": { "d3-array": "2.5.0 - 3" }, @@ -2400,7 +2402,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "dev": true, "engines": { "node": ">=12" } @@ -2409,7 +2410,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dev": true, "dependencies": { "d3-color": "1 - 3" }, @@ -2421,7 +2421,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "dev": true, "engines": { "node": ">=12" } @@ -2430,7 +2429,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "dev": true, "engines": { "node": ">=12" } @@ -2439,7 +2437,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "dev": true, "engines": { "node": ">=12" } @@ -2448,7 +2445,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "dev": true, "engines": { "node": ">=12" } @@ -2457,7 +2453,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dev": true, "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -2473,7 +2468,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "dev": true, "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -2486,7 +2480,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "dev": true, "engines": { "node": ">=12" } @@ -2495,7 +2488,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dev": true, "dependencies": { "d3-path": "^3.1.0" }, @@ -2507,7 +2499,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dev": true, "dependencies": { "d3-array": "2 - 3" }, @@ -2519,7 +2510,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dev": true, "dependencies": { "d3-time": "1 - 3" }, @@ -2531,7 +2521,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "dev": true, "engines": { "node": ">=12" } @@ -2540,7 +2529,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dev": true, "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", @@ -2559,7 +2547,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dev": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -2691,7 +2678,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "dev": true, "dependencies": { "robust-predicates": "^3.0.2" } @@ -3560,7 +3546,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3619,11 +3604,18 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "dev": true, "engines": { "node": ">=12" } }, + "node_modules/interval-tree-1d": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", + "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", + "dependencies": { + "binary-search-bounds": "^2.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -3980,6 +3972,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isoformat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz", + "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==" + }, "node_modules/iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -4684,8 +4681,7 @@ "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "dev": true + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { "version": "4.20.0", @@ -4748,8 +4744,7 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "dev": true + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/safe-array-concat": { "version": "1.1.2", @@ -4789,8 +4784,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/scheduler": { "version": "0.23.2", diff --git a/package.json b/package.json index e0fa826..29f3474 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@mui/material": "^5.16.6", + "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", "react-resizable-panels": "^2.0.23", diff --git a/src/components/Diagram.jsx b/src/components/Diagram.jsx index e69de29..4c82b58 100644 --- a/src/components/Diagram.jsx +++ b/src/components/Diagram.jsx @@ -0,0 +1,14 @@ +import * as Plot from "@observablehq/plot"; +import Document from "../utils/Document" + + +/** + * + * @param {DiagramEntity} diagramEntity + * @returns + */ +export default function Diagram({ diagramEntity }) { + if (!diagramEntity) { return <> } + const plotOptions = diagramEntity.generatePlotOptions() + return Plot.plot({ ...plotOptions, document: new Document() }).toHyperScript(); +} \ No newline at end of file diff --git a/src/constants/DiagramType.js b/src/constants/DiagramType.js index deb40e4..dda9080 100644 --- a/src/constants/DiagramType.js +++ b/src/constants/DiagramType.js @@ -1,22 +1,77 @@ +import * as Plot from "@observablehq/plot" + +/** + * Options shared by all diagram types. + */ +const COMMON_OPTIONS = [ + "gridX", + "gridY", + "colorScheme" +] + +/** + * Helper function that generates plot options from common options. + */ +function commonPlotOptions(options) { + return { + y: { grid: options.gridY }, + color: { scheme: options.colorScheme }, + } +} + + + +/** + * Contains necessary information of all available diagram types. + * The following types are currently available: + * @param {object} HIST Histogram. + * @param {object} SCATTER Scatter plot. + * + * Each type must contain the following information: + * @param {string} name Name of the type. + * @param {object} options All possible configuration options. + * @param {object} requiredOptions Options that must not be left blank. + * @param {function} plotOptions A function that generate option objects used for funcion `Plot.plot`. + */ const DIAGRAM_TYPE = {} + DIAGRAM_TYPE.HIST = { name: "Histogram", - requiredConfig: ["x", "xmin", "xmax", "ymin", "ymax", "bin"], - optionalConfig: [] + options: ["x"].concat(COMMON_OPTIONS), + requiredOptions: ["x"], + plotOptions: (data, options) => { + const plotOptions = commonPlotOptions(options) + plotOptions.marks = [ + Plot.ruleY([0]), + Plot.rectY(data, Plot.binX({y: "count"}, {x: options.x})) + ] + return plotOptions + } } DIAGRAM_TYPE.SCATTER = { name: "Scatter", - requiredConfig: ["x", "y", "xmin", "xmax", "ymin", "ymax"], - optionalConfig: [] + options: ["x", "y"].concat(COMMON_OPTIONS), + requiredOptions: ["x", "y"], + plotOptions: (data, options) => { + const plotOptions = commonPlotOptions(options) + plotOptions.marks = [ + Plot.ruleY([0]), + Plot.dot(data, {x: options.x, y: options.y}) + ] + return plotOptions + }, } + + + Object.freeze(DIAGRAM_TYPE) export default DIAGRAM_TYPE \ No newline at end of file diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index bd94f22..56888e0 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -1,17 +1,55 @@ +import { DiagramTypeError } from "../exceptions/DataExceptions"; import DataEntity from "./DataEntity"; export default class DiagramEntity { /** - * @param {object} type - * @param {DataEntity} dataEntity + * @param {object} type Type of diagram. Must be one of the types in `DIAGRAM_TYPE`. + * @param {DataEntity} linkedData The DataEntity whose data is used for the diagram. + * @param {object} options Configuration for the plotting result. */ - constructor(type, dataEntity, config={}) { + constructor(type, linkedData, options={}) { this.type = type - this.linkedData = dataEntity - this.config = config + this.linkedData = linkedData + this.options = options + this._setDefaultOptions() } + _setDefaultOptions() { + this.options.gridY = true + this.options.colorScheme = "burd" + } + + setOption(key, value) { + if (value === null || value === undefined) { + delete this.options[key] + return + } + if (this.type.options.includes(key)) { + this.options[key] = value + return + } + throw new DiagramTypeError(`"${key}" is not a valid option for ${this.type.name} diagram. + Valid options are ${this.type.options}`) + } + + getOption(key) { + return this.options[key] + } + /** + * Generates a configuration object that can be used by the function `Plot.plot`. + * @returns {object} + */ + generatePlotOptions() { + return this.type.plotOptions(this.linkedData.data, this.options) + } + + isPlotable() { + for (let i = 0; i < this.type.requiredOptions.length; i++) { + if (!(this.type.requiredOptions[i] in this.options)) { return false } + } + return true + } } \ No newline at end of file diff --git a/src/exceptions/DataExceptions.js b/src/exceptions/DataExceptions.js index 54ae099..9be8171 100644 --- a/src/exceptions/DataExceptions.js +++ b/src/exceptions/DataExceptions.js @@ -12,7 +12,15 @@ class QuestionnaireTypeError extends Error { } } +class DiagramTypeError extends Error { + constructor(message) { + super(message) + this.name = this.constructor.name + } +} + export { InvalidDataInputError, - QuestionnaireTypeError + QuestionnaireTypeError, + DiagramTypeError } \ No newline at end of file diff --git a/src/utils/DiagramUtils.js b/src/utils/DiagramUtils.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/Document.js b/src/utils/Document.js new file mode 100644 index 0000000..dee0f3d --- /dev/null +++ b/src/utils/Document.js @@ -0,0 +1,126 @@ +import { createElement as h } from "react"; + +export default class Document { + constructor() { + this.documentElement = new Element(this, "html"); + } + createElementNS(namespace, tagName) { + return new Element(this, tagName); + } + createElement(tagName) { + return new Element(this, tagName); + } + createTextNode(value) { + return new TextNode(this, value); + } + querySelector() { + return null; + } + querySelectorAll() { + return []; + } +} + +class Style { + static empty = new Style(); + setProperty() {} + removeProperty() {} +} + +class Element { + constructor(ownerDocument, tagName) { + this.ownerDocument = ownerDocument; + this.tagName = tagName; + this.attributes = {}; + this.children = []; + this.parentNode = null; + } + setAttribute(name, value) { + this.attributes[name] = String(value); + } + setAttributeNS(namespace, name, value) { + this.setAttribute(name, value); + } + getAttribute(name) { + return this.attributes[name]; + } + getAttributeNS(name) { + return this.getAttribute(name); + } + hasAttribute(name) { + return name in this.attributes; + } + hasAttributeNS(name) { + return this.hasAttribute(name); + } + removeAttribute(name) { + delete this.attributes[name]; + } + removeAttributeNS(namespace, name) { + this.removeAttribute(name); + } + addEventListener() { + // ignored; interaction needs real DOM + } + removeEventListener() { + // ignored; interaction needs real DOM + } + dispatchEvent() { + // ignored; interaction needs real DOM + } + append(...children) { + for (const child of children) { + this.appendChild( + child?.ownerDocument ? child : this.ownerDocument.createTextNode(child) + ); + } + } + appendChild(child) { + this.children.push(child); + child.parentNode = this; + return child; + } + insertBefore(child, after) { + if (after == null) { + this.children.push(child); + } else { + const i = this.children.indexOf(after); + if (i < 0) throw new Error("insertBefore reference node not found"); + this.children.splice(i, 0, child); + } + child.parentNode = this; + return child; + } + querySelector() { + return null; + } + querySelectorAll() { + return []; + } + set textContent(value) { + this.children = [this.ownerDocument.createTextNode(value)]; + } + set style(value) { + this.attributes.style = value; + } + get style() { + return Style.empty; + } + toHyperScript() { + return h( + this.tagName, + this.attributes, + this.children.map((c) => c.toHyperScript()) + ); + } +} + +class TextNode { + constructor(ownerDocument, nodeValue) { + this.ownerDocument = ownerDocument; + this.nodeValue = String(nodeValue); + } + toHyperScript() { + return this.nodeValue; + } +} From ad3e5e956ae59524c2b34fb01eda0e273094fd53 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 19 Sep 2024 20:48:41 +0800 Subject: [PATCH 21/65] Fix import in DataAnalysisService --- src/services/DataAnalysisService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 94ddd16..13b3f31 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -1,4 +1,4 @@ -import { DataEntity } from "../entities/DataEntity" +import DataEntity from "../entities/DataEntity" import { average } from "../utils/MathUtils" From 756b4811b9615399e50a4a919aa0259ef973641e Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Tue, 24 Sep 2024 22:44:26 +0200 Subject: [PATCH 22/65] D3VisualizationGraph --- .../workspace/visualization/Visualization.jsx | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/app/pages/workspace/visualization/Visualization.jsx b/src/app/pages/workspace/visualization/Visualization.jsx index 7444edc..33f7d72 100644 --- a/src/app/pages/workspace/visualization/Visualization.jsx +++ b/src/app/pages/workspace/visualization/Visualization.jsx @@ -5,34 +5,37 @@ * @returns {ReactNode} Rendered component. */ -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +export default function Visualization() { + const chartRef = useRef(null); -const data = [ - { name: 'sampleA', v1: 4000, v2: 2000, v3: 1000 }, - { name: 'sampleB', v1: 3000, v2: 1000, v3: 2000 }, - { name: 'sampleC', v1: 2000, v2: 7000, v3: 2090 }, -]; + useEffect(() => { + const chart = Plot.plot({ + marks: [ + Plot.barY(data, { x: "name", y: "value" }), + ], + x: { + label: "Pages", + }, + y: { + label: "Values", + }, + }); -export default function Visualization() { - return ( - - - - - - - - - - - + if (chartRef.current) { + chartRef.current.appendChild(chart); + } - ) + return () => { + if (chartRef.current) { + chartRef.current.innerHTML = ''; + } + }; + }, []); + + return ( +
+

Bar Chart Visualization

+
{} +
+ ); } From 0a6c12ba89e746b2163fead494f470ce91fc9ca5 Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Wed, 25 Sep 2024 09:32:00 +0200 Subject: [PATCH 23/65] UEQ and raxTXTL logic --- src/constants/QuestionnaireType.js | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/constants/QuestionnaireType.js b/src/constants/QuestionnaireType.js index a806395..ec63ddb 100644 --- a/src/constants/QuestionnaireType.js +++ b/src/constants/QuestionnaireType.js @@ -31,6 +31,57 @@ QUESTIONNAIRE_TYPE.NONE = { } } +QUESTIONNAIRE_TYPE.UEQ = { + name: "UEQ", + numOfQuestions: 26, + minValue: -3, + maxValue: 3, + // Categories of the UEQ + categories: { + attractiveness: [1, 2, 3, 4, 5, 6], + perspicuity: [7, 8, 9, 10, 11, 12], + efficiency: [13, 14, 15, 16], + dependability: [17, 18, 19, 20], + stimulation: [21, 22, 23], + novelty: [24, 25, 26] + }, + + // Calculate UEQ-Score + scoreCalculator: (ueqResult) => { + let scores = {}; + + // UEQ-Score pro Category + for (let category in QUESTIONNAIRE_TYPE.UEQ.categories) { + let questionIndices = QUESTIONNAIRE_TYPE.UEQ.categories[category]; + let categoryScoreUEQ = 0; + + // Sum of questions pro category + for (let i = 0; i < questionIndices.length; i++) { + let qIndex = questionIndices[i]; + categoryScoreUEQ += ueqResult["Q" + qIndex + 1]; + } + + // mean for every category + scores[category] = categoryScoreUEQ / questionIndices.length; + } + + return scores; + }, + + scoreInterpretor: (categoryScoreUEQ) => { + if (categoryScoreUEQ > 1.5) { + return "Excellent"; + } else if (categoryScoreUEQ > 0.5) { + return "Good"; + } else if (categoryScoreUEQ >= -0.5) { + return "Neutral"; + } else { + return "Poor"; + } + } + +} + QUESTIONNAIRE_TYPE.SUS = { name: "SUS", @@ -66,7 +117,34 @@ QUESTIONNAIRE_TYPE.SUS = { } } +QUESTIONNAIRE_TYPE.rawTLX = { + name: "rawTLX", + numOfQuestions: 6, + minValue: 0, + maxValue: 20, + scoreCalculator: (rawtxlresults) => { + let score = 0; + + // rawTLX-Score + for (let i = 1; i <= 6; i++) { + score += rawtxlresults[`Q${i}`]; // sum the scores + } + //final rule + return score / 6; + }, + + scoreInterpretor: (rawtxtlscore) => { + let scaledtxlscore = rawtxtlscore * 5; + if (scaledtxlscore >= 75) { + return "Very High Workload"; + } else if (scaledtxlscore >= 25) { + return "Moderate Workload"; + } else { + return "Very Low Workload"; + } + } +} Object.freeze(QUESTIONNAIRE_TYPE) From c97b527a46b7dda9e09fdb0b959832e9b8bdd880 Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Thu, 26 Sep 2024 08:26:36 +0200 Subject: [PATCH 24/65] NPSScore --- src/constants/QuestionnaireType.js | 41 ++++++++++++++++++++++ tests/services/DataAnalysisService.test.js | 13 +++++++ 2 files changed, 54 insertions(+) diff --git a/src/constants/QuestionnaireType.js b/src/constants/QuestionnaireType.js index ec63ddb..5019dfe 100644 --- a/src/constants/QuestionnaireType.js +++ b/src/constants/QuestionnaireType.js @@ -146,6 +146,47 @@ QUESTIONNAIRE_TYPE.rawTLX = { } } +QUESTIONNAIRE_TYPE.NPS = { + name: "NPS", + numOfQuestions: 1, + minValue: 0, + maxValue: 10, + + scoreCalculator: (npsresults) => { + let promoters = 0; + let detractors = 0; + let neutrals = 0; + let numberResponses = npsresults.length; + + // nps-Score + for (let i = 0; i < npsresults.length; i++) { + let score = npsresults[i] + + if (score >= 9){ + promoters++; + } + else if (score <= 6){ + detractors++; + } + if{ + neutrals++; + } + } + let npsScore = ((promoters - detractors)/numberResponses)*100; + return npsScore; + }, + + scoreInterpretor: (npsScore) => { + if (npsScore >= 30) { + return "Good NPS"; + } else if (npsScore >= -30) { + return "Moderate NPS"; + } else { + return "Bad NPS"; + } + } +} + Object.freeze(QUESTIONNAIRE_TYPE) diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js index 22fb86b..1ec45ba 100644 --- a/tests/services/DataAnalysisService.test.js +++ b/tests/services/DataAnalysisService.test.js @@ -33,4 +33,17 @@ test( } ) +test( + "DataAnalysisService calculates NPS correctly.", + () => { + const testDataEntity = generateDataEntity( + QUESTIONNAIRE_TYPE.NPS, + [[1,8,8,7,9,9,9,9] + ] + ) + const npsScore = dataAnalysisService.calculateAverageScore(testDataEntity) + assert(Math.abs(npsScore - 37.5) < 0.1, "Test NPS data should have NPS score 37.5.") + } +) + From 4a045c056d64b050010013016c709d86749d6c36 Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:20:05 +0200 Subject: [PATCH 25/65] Update DataTableTabs.jsx Implementing different tabs to view data tables from different csv files --- .../workspace/datatables/DataTableTabs.jsx | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index 6c30965..ddb1270 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -4,8 +4,60 @@ * * @returns {ReactNode} Rendered tabs component. */ + export default function DataTableTabs() { + const [activeTab, setActiveTab] = useState(0); + const [dataTables, setDataTables] = useState([]); + + useEffect(() => { + Promise.all(csvFiles.map(file => d3.csv(file))) + .then(data => { + setDataTables(data); + }) + .catch(error => { + console.error('Error', error); + }); + }, []); + return ( -

Data Table

- ) +
+
+ {dataTables.map((_, index) => ( + + ))} +
+ +
+ {dataTables.length > 0 ? ( + + + + {Object.keys(dataTables[activeTab][0]).map((columnName, i) => ( + + ))} + + + + {dataTables[activeTab].map((row, i) => ( + + {Object.values(row).map((value, j) => ( + + ))} + + ))} + +
{columnName}
{value}
+ ) : ( +

Loading...

+ )} +
+
+ ); + } \ No newline at end of file From e8eb0bb8b4bd97fa994239e844b3bd098ccd80ba Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 26 Sep 2024 10:52:29 +0200 Subject: [PATCH 26/65] Add tests to DiagramEntity --- src/entities/DiagramEntity.js | 66 ++++++++++++++++++++++++---- tests/entities/DiagramEntity.test.js | 40 +++++++++++++++++ 2 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 tests/entities/DiagramEntity.test.js diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index 56888e0..b32afc0 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -11,8 +11,11 @@ export default class DiagramEntity { constructor(type, linkedData, options={}) { this.type = type this.linkedData = linkedData - this.options = options + this.options = {} this._setDefaultOptions() + Object.keys(options).forEach(key => { + this.setOption(key, options[key]) + }) } _setDefaultOptions() { @@ -20,17 +23,53 @@ export default class DiagramEntity { this.options.colorScheme = "burd" } + /** + * Set options to configure diagram resulf. Possible keys can be found in `DiagramType.js`. + * @param {string} key + * @param {any} value + * @returns + */ setOption(key, value) { if (value === null || value === undefined) { delete this.options[key] return } - if (this.type.options.includes(key)) { - this.options[key] = value - return + if (!this.type.options.includes(key)) { + throw new DiagramTypeError(`"${key}" is not a valid option for ${this.type.name} diagram. + Valid options are ${this.type.options}.`) + } + if (key === "x") { + if (!this.linkedData.allColumns.includes(value)) { + throw new DiagramTypeError(`DataEntity ${this.linkedData} does not contain column ${value}`) + } + } + else if (key === "y") { + if (!this.linkedData.allColumns.includes(value)) { + throw new DiagramTypeError(`DataEntity ${this.linkedData} does not contain column "${value}"`) + } } - throw new DiagramTypeError(`"${key}" is not a valid option for ${this.type.name} diagram. - Valid options are ${this.type.options}`) + this.options[key] = value + } + + /** + * Link DiagramEntity to another DataEntity. Reset `x` and `y` options. + * @param {DataEntity} dataEntity + */ + setLinkedData(dataEntity) { + this.linkedData = dataEntity + delete this.options.x + delete this.options.y + } + + /** + * Set diagram type. Delete all unsupported options. + * @param {object} type + */ + setType(type) { + this.type = type + Object.keys(this.options).forEach(k => { + if (!(k in type.options)) { delete this.options[k] } + }) } getOption(key) { @@ -42,12 +81,23 @@ export default class DiagramEntity { * @returns {object} */ generatePlotOptions() { + this.checkPlotability() return this.type.plotOptions(this.linkedData.data, this.options) } - isPlotable() { + /** + * Checks if the diagram entity is ready for plotting. + * All required options of the diagram type should be filled. + * @returns + */ + checkPlotability() { for (let i = 0; i < this.type.requiredOptions.length; i++) { - if (!(this.type.requiredOptions[i] in this.options)) { return false } + let requiredOption = this.type.requiredOptions[i] + if (!(requiredOption in this.options)) { + throw new DiagramTypeError(`Required option ${requiredOption} is missing.`) + } + } + if ("y" in this.options) { } return true } diff --git a/tests/entities/DiagramEntity.test.js b/tests/entities/DiagramEntity.test.js new file mode 100644 index 0000000..cce0da3 --- /dev/null +++ b/tests/entities/DiagramEntity.test.js @@ -0,0 +1,40 @@ +import { test, assert, expect } from "vitest"; +import { generateDataEntity } from "../TestUtils"; +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType"; +import DiagramEntity from "../../src/entities/DiagramEntity"; +import DIAGRAM_TYPE from "../../src/constants/DiagramType"; +import { DiagramTypeError } from "../../src/exceptions/DataExceptions"; + + +const dataEntity = generateDataEntity( + QUESTIONNAIRE_TYPE.NONE, + [ + [1,2,3], + [4,5,6], + [7,8,9] + ] +) + +dataEntity.addUserInfoColumns("age") + +const diagramEntity = new DiagramEntity( + DIAGRAM_TYPE.HIST, + dataEntity, + { + gridY: false + } +) + + +test( + "Option getter and setter work correctly.", + () => { + assert(diagramEntity.getOption("gridY") === false) + diagramEntity.setOption("x", "age") + assert(diagramEntity.getOption("x") === "age") + expect(() => diagramEntity.setOption("fake-option", 0)) + .toThrow(DiagramTypeError) + expect(() => diagramEntity.setOption("x", "fake-column")) + .toThrow(DiagramTypeError) + } +) From 28fba6275f523f189760a8cb1a7194b3841dcf44 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 26 Sep 2024 14:36:52 +0200 Subject: [PATCH 27/65] Minor fixes --- .../workspace/visualization/Visualization.jsx | 31 +------------------ src/constants/QuestionnaireType.js | 2 +- src/entities/WorkspaceEntity.js | 2 -- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/app/pages/workspace/visualization/Visualization.jsx b/src/app/pages/workspace/visualization/Visualization.jsx index 33f7d72..86b50aa 100644 --- a/src/app/pages/workspace/visualization/Visualization.jsx +++ b/src/app/pages/workspace/visualization/Visualization.jsx @@ -4,38 +4,9 @@ * * @returns {ReactNode} Rendered component. */ - export default function Visualization() { - const chartRef = useRef(null); - - useEffect(() => { - const chart = Plot.plot({ - marks: [ - Plot.barY(data, { x: "name", y: "value" }), - ], - x: { - label: "Pages", - }, - y: { - label: "Values", - }, - }); - - if (chartRef.current) { - chartRef.current.appendChild(chart); - } - - return () => { - if (chartRef.current) { - chartRef.current.innerHTML = ''; - } - }; - }, []); return ( -
-

Bar Chart Visualization

-
{} -
+

Visualization

); } diff --git a/src/constants/QuestionnaireType.js b/src/constants/QuestionnaireType.js index 5019dfe..537b265 100644 --- a/src/constants/QuestionnaireType.js +++ b/src/constants/QuestionnaireType.js @@ -168,7 +168,7 @@ QUESTIONNAIRE_TYPE.NPS = { else if (score <= 6){ detractors++; } - if{ + else{ neutrals++; } } diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js index ae4fe15..cffb897 100644 --- a/src/entities/WorkspaceEntity.js +++ b/src/entities/WorkspaceEntity.js @@ -1,5 +1,3 @@ export default class WorkspaceEntity { - - // test pull } \ No newline at end of file From 675b2c3cd08c4a1a71ffe032db3e28835d991367 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 27 Sep 2024 22:40:39 +0200 Subject: [PATCH 28/65] Add tutorials --- README.md | 7 ++ docs/tutorials/tutorial-entities.md | 156 ++++++++++++++++++++++++++++ docs/tutorials/tutorial-services.md | 29 ++++++ src/entities/DataEntity.js | 25 +---- src/entities/DiagramEntity.js | 9 +- 5 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 docs/tutorials/tutorial-entities.md create mode 100644 docs/tutorials/tutorial-services.md diff --git a/README.md b/README.md index c23eb5a..15932d0 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ $ npm run dev ## Technologies - Vite +- Vitest - React - D3.js - Material UI @@ -30,3 +31,9 @@ $ npm run dev ### Developer guidelines See [GUIDELINES.md](docs/GUIDELINES.md) + +### Tutorials for developers + +[Tutorial on Entities](docs/tutorials/tutorial-entities.md) + +[Tutorial on Services](docs/tutorials/tutorial-services.md) diff --git a/docs/tutorials/tutorial-entities.md b/docs/tutorials/tutorial-entities.md new file mode 100644 index 0000000..8fa9414 --- /dev/null +++ b/docs/tutorials/tutorial-entities.md @@ -0,0 +1,156 @@ +# Tutorial on Entities + +## `DataEntity` + +The class `DataEntity` stores a table of questionnaire data and user information. A `DataEntity` contains information of its questionnaire type, data and columns. The following is an example of a `DataEntity` of rawTLX questionnaire with 2 rows of data. + +```javascript +DataEntity { + type: { + name: 'rawTLX', + numOfQuestions: 6, + minValue: 0, + maxValue: 20, + scoreCalculator: [Function: scoreCalculator], + scoreInterpretor: [Function: scoreInterpretor] + }, + data: + [ + { Q1: 11, Q2: 18, Q3: 18, Q4: 17, Q5: 19, Q6: 9, id: 1 }, + { Q1: 13, Q2: 2, Q3: 10, Q4: 14, Q5: 17, Q6: 18, id: 2 } + ], + columns: { + userInfo: [ 'id' ], + questions: [ + 'Q1', 'Q2', 'Q3', + 'Q4', 'Q5', 'Q6', + 'Q7', 'Q8' + ] + } +} +``` + +### Constructor + +The constructor of `DataEntity` takes in questionnaire type and data, both are optional. The constructor automatically detect questionnaire columns and user info columns. + +```javascript +const dataEntity = new DataEntity(QUESTIONNAIRE_TYPE.NONE, + [ + { Q1: 5, Q2: 5, Q3: 5, id: 1 }, + ] + ) +console.log(dataEntity) +// Result: +// +// DataEntity { +// type: { +// name: 'NONE', +// numOfQuestions: 9007199254740991, +// minValue: -9007199254740991, +// maxValue: 9007199254740991, +// scoreCalculator: [Function: scoreCalculator], +// scoreInterpretor: [Function: scoreInterpretor] +// }, +// data: +// [ +// { Q1: 5, Q2: 5, Q3: 5, id: 1 } +// ], +// columns: { +// userInfo: [ 'id' ], +// questions: [ 'Q1', 'Q2', 'Q3' ] +// } +// } + +``` + +### Column operations + +| Function | Description | Notes | +| --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------ | +| addQuestions(numOfQuestions=1) | Add given number of new questions to the questionnaire. | Only NONE-type data allows adding new questions. | +| deleteQuestions(numOfQuestions=1) | Delete given number of new questions to the questionnaire. | Only NONE-type data allows setting number of questions | +| addUserInfoColumns(columns) | Given a string or an array of strings, add new strings among them as user info columns. | Existing columns will not be added again. | +| deleteUserInfoColumns(columns) | Given a string or an array of strings, delete user info columns of those names. | | + +### Row operations + +| Function | Description | +| ---------------------------- | ---------------------------------------------- | +| addEmptyRows(numberOfRows=1) | Append given number of empty rows to the data. | +| insertEmptyRow(index) | Insert 1 empty row at the given index. | + +### Data manipulation + +| Function | Description | +| ------------------------------ | ------------------------------------------------- | +| setValue(rowNr, column, value) | Set value at the given and row number and column. | +| setType(type) | Set questionnaire type. | + +### Getters + +| Function | Description | +| --------------------- | ------------------------------------------------- | +| get size() | Get number of rows of data. | +| get numOfQuestions() | Get number of questions. | +| get allColumns() | Get a list of all columns. | +| get userInfoColumns() | Get a list of user info columns. | +| get questionColumns() | Get a list of question columns. | +| getType() | Get a string of questionnaire type name. | +| row(rowNumber) | Get a single row of data. | +| loc(rowNr, column) | Get the value at the given row number and column. | + +### Example + +Using the same `dataEntity` we created earlier: + +```javascript +console.log(dataEntity.getType()) +// Result: NONE + +dataEntity.addQuestions() +console.log(dataEntity.questionColumns) +// Result: [ 'Q1', 'Q2', 'Q3', 'Q4' ] + +dataEntity.addUserInfoColumns(["id", "age"]) +dataEntity.addUserInfoColumns("gender") +console.log(dataEntity.allColumns) +// Result" [ 'id', 'age', 'gender', 'Q1', 'Q2', 'Q3', 'Q4' ] + +dataEntity.addEmptyRows(2) +dataEntity.setValue(2, "Q1", 1000) +console.log(dataEntity.loc(2, "Q1")) +// Result: 1000 +console.log(dataEntity) +// Result: +// +// DataEntity { +// type: { +// name: 'NONE', +// numOfQuestions: 9007199254740991, +// minValue: -9007199254740991, +// maxValue: 9007199254740991, +// scoreCalculator: [Function: scoreCalculator], +// scoreInterpretor: [Function: scoreInterpretor] +// }, +// data: +// [ +// { Q1: 5, Q2: 5, Q3: 5, Q4: null, id: 1, age: null, gender: null }, +// { Q1: null, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, +// { Q1: 1000, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, +// ], +// columns: { +// userInfo: [ 'id', 'age', 'gender' ], +// questions: [ 'Q1', 'Q2', 'Q3', 'Q4' ] +// } +// } +``` + +## `DiagramEntity` + +`DiagramEntity` is a data structure to store all information needed to plot a single diagram. It contains a diagram type, a reference to its data source (a `DataEntity`), and options for plotting. + +This class is relatively easy to understand. You can refer to the source code in `src\entities\DiagramEntity.js` for more information. + + + diff --git a/docs/tutorials/tutorial-services.md b/docs/tutorials/tutorial-services.md new file mode 100644 index 0000000..29c7d01 --- /dev/null +++ b/docs/tutorials/tutorial-services.md @@ -0,0 +1,29 @@ +# Tutorial on Services + +Services are global objects that provide static utility functions. An example: + +```javascript +import dataService from "../../src/services/DataService" +import dataAnalysisService from "../../src/services/DataAnalysisService" +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" + +dataService.importData("./tests/test-data/SUS-example.csv", QUESTIONNAIRE_TYPE.SUS) + .then(dataEntity => { + const scores = dataAnalysisService.calculateScores(dataEntity) + console.log(scores) + }) +``` + +Service objects are usually straightforward. You are encouraged to refer to the source code in `src\services` to learn about them. + +## `dataService` + +This service deal with import/export of `DataEntity`. + +Particularly, `dataService.importData` is a asynchronous function that generates a `DataEntity` from a CSV file. + +If you are not familiar with asynchronous functions, see https://www.w3schools.com/js/js_async.asp. + +## `dataAnalysisService` + +This service deal with data analysis. It calculated scores base on questionnaire type. \ No newline at end of file diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 4d4f89d..2171af7 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,11 +1,10 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { InvalidDataInputError, QuestionnaireTypeError } from "../exceptions/DataExceptions" -import { generateEmptyRow, generateResultColumns, isQuestionColumn } from "../utils/DataUtils" +import { generateEmptyRow, isQuestionColumn } from "../utils/DataUtils" /** * A class for representing a series of questionnaire data. - * */ export default class DataEntity { @@ -99,7 +98,6 @@ export default class DataEntity { - /** /************************* * Row operations *************************/ @@ -115,7 +113,7 @@ export default class DataEntity { } /** - * In sert 1 empty row at the given index. + * Insert 1 empty row at the given index. * @param {number} index */ insertEmptyRow(index) { @@ -164,7 +162,7 @@ export default class DataEntity { } /** - * Set value at given column and row number. + * Set value at given row number and column. * @param {number} rowNr * @param {string} column * @param {number} value @@ -197,24 +195,11 @@ export default class DataEntity { get questionColumns() { return this.columns.questions } + getType() { return this.type.name } + row(rowNumber) { return this.data[rowNumber] } loc(rowNr, column) { return this.data[rowNr][column] } } - -/** - * A class that contains information about users who took - * the questionnaire. - */ -// class UserInfo { - -// constructor(userID=null, age=null, gender=null, education=null) { -// this.userID = userID; -// this.age = age; -// this.gender = gender; -// this.education = education; -// } - -// } diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index b32afc0..d9690c8 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -24,7 +24,8 @@ export default class DiagramEntity { } /** - * Set options to configure diagram resulf. Possible keys can be found in `DiagramType.js`. + * Set options to configure diagram resulf. Possible options can be found in `DiagramType.js` or using + * the getter `supportedOptions`. * @param {string} key * @param {any} value * @returns @@ -87,7 +88,7 @@ export default class DiagramEntity { /** * Checks if the diagram entity is ready for plotting. - * All required options of the diagram type should be filled. + * All required options of the diagram type must be filled. * @returns */ checkPlotability() { @@ -102,4 +103,8 @@ export default class DiagramEntity { return true } + get supportedOptions() { return this.type.options } + + get requiredOptions() { return this.type.requiredOptions} + } \ No newline at end of file From c9dae0ffa8710c16927fe23c54153613bdb20b25 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sat, 28 Sep 2024 08:58:51 +0200 Subject: [PATCH 29/65] Minor updates on entities --- docs/tutorials/tutorial-entities.md | 3 ++- src/entities/DiagramEntity.js | 39 ++++++++++++++++------------ src/entities/WorkspaceEntity.js | 13 ++++++++++ tests/entities/DiagramEntity.test.js | 12 ++++++++- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/docs/tutorials/tutorial-entities.md b/docs/tutorials/tutorial-entities.md index 8fa9414..ad2d255 100644 --- a/docs/tutorials/tutorial-entities.md +++ b/docs/tutorials/tutorial-entities.md @@ -152,5 +152,6 @@ console.log(dataEntity) This class is relatively easy to understand. You can refer to the source code in `src\entities\DiagramEntity.js` for more information. +## `WorkspaceEntity` - +The sole purpose of `WorkspaceEntity` is to store current `dataEntity` and `diagramEntity`. diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index d9690c8..fa2f401 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -2,12 +2,12 @@ import { DiagramTypeError } from "../exceptions/DataExceptions"; import DataEntity from "./DataEntity"; export default class DiagramEntity { - + /** * @param {object} type Type of diagram. Must be one of the types in `DIAGRAM_TYPE`. * @param {DataEntity} linkedData The DataEntity whose data is used for the diagram. * @param {object} options Configuration for the plotting result. - */ + */ constructor(type, linkedData, options={}) { this.type = type this.linkedData = linkedData @@ -17,12 +17,16 @@ export default class DiagramEntity { this.setOption(key, options[key]) }) } - + + /** + * A helper function to set default options. Must not be used from outside of the class. + */ _setDefaultOptions() { this.options.gridY = true this.options.colorScheme = "burd" } - + + /** * Set options to configure diagram resulf. Possible options can be found in `DiagramType.js` or using * the getter `supportedOptions`. @@ -73,38 +77,39 @@ export default class DiagramEntity { }) } - getOption(key) { - return this.options[key] - } - + /** - * Generates a configuration object that can be used by the function `Plot.plot`. + * Generates a configuration object that can be used by the function `Plot.plot`. This function + * must not be used from outside of `src/conponents/Diagram.jsx`. * @returns {object} - */ + */ generatePlotOptions() { this.checkPlotability() return this.type.plotOptions(this.linkedData.data, this.options) } - + /** * Checks if the diagram entity is ready for plotting. * All required options of the diagram type must be filled. * @returns - */ + */ checkPlotability() { for (let i = 0; i < this.type.requiredOptions.length; i++) { let requiredOption = this.type.requiredOptions[i] if (!(requiredOption in this.options)) { - throw new DiagramTypeError(`Required option ${requiredOption} is missing.`) + return false } } - if ("y" in this.options) { - } return true } + get supportedOptions() { return this.type.options } - - get requiredOptions() { return this.type.requiredOptions} + + get requiredOptions() { return this.type.requiredOptions } + + getOption(key) { + return this.options[key] + } } \ No newline at end of file diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js index cffb897..d9056ed 100644 --- a/src/entities/WorkspaceEntity.js +++ b/src/entities/WorkspaceEntity.js @@ -1,3 +1,16 @@ export default class WorkspaceEntity { + constructor(dataEntity=null, diagramEntity=null) { + this.dataEntity = dataEntity + this.diagramEntity = diagramEntity + } + + setDataEntity(dataEntity) { + this.dataEntity = dataEntity + } + + setDiagramEntity() { + this.diagramEntity = diagramEntity + } + } \ No newline at end of file diff --git a/tests/entities/DiagramEntity.test.js b/tests/entities/DiagramEntity.test.js index cce0da3..cb0521d 100644 --- a/tests/entities/DiagramEntity.test.js +++ b/tests/entities/DiagramEntity.test.js @@ -18,7 +18,7 @@ const dataEntity = generateDataEntity( dataEntity.addUserInfoColumns("age") const diagramEntity = new DiagramEntity( - DIAGRAM_TYPE.HIST, + DIAGRAM_TYPE.SCATTER, dataEntity, { gridY: false @@ -38,3 +38,13 @@ test( .toThrow(DiagramTypeError) } ) + + +test( + "Delete unsupported options when diagram type changes.", + () => { + diagramEntity.setOption("y", "Q1") + diagramEntity.setType(DIAGRAM_TYPE.HIST) + assert(diagramEntity.getOption("y") === undefined) + } +) From 09a596a7c5fc99e0c893b28d25a2d29e49bbef37 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 30 Sep 2024 12:01:22 +0200 Subject: [PATCH 30/65] Add workspace and states provider --- src/app/App.jsx | 5 +++++ src/app/pages/workspace/Workspace.jsx | 5 +++++ src/providers/StatesProvider.jsx | 27 +++++++++++++++++++++++++++ src/providers/WorkspaceProvider.jsx | 17 +++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 src/providers/StatesProvider.jsx create mode 100644 src/providers/WorkspaceProvider.jsx diff --git a/src/app/App.jsx b/src/app/App.jsx index f0be19b..0307ec5 100644 --- a/src/app/App.jsx +++ b/src/app/App.jsx @@ -1,15 +1,20 @@ +import { WorkspaceProvider } from '../providers/WorkspaceProvider'; import Home from './pages/home/Home' import Workspace from './pages/workspace/Workspace' import { BrowserRouter, Routes, Route } from "react-router-dom"; function App() { return ( + + } /> } /> + + ) } diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx index 45d3e2b..c815eb7 100644 --- a/src/app/pages/workspace/Workspace.jsx +++ b/src/app/pages/workspace/Workspace.jsx @@ -4,6 +4,7 @@ import DataTable from "./datatables/DataTableTabs" import Visualization from "./visualization/Visualization" import Modification from "./modification/Modification" import WorkspaceEntity from "../../../entities/WorkspaceEntity" +import { StatesProvider } from "../../../providers/StatesProvider" /** * Main user interface for data analysis. Contains `DataTable`, @@ -14,6 +15,8 @@ import WorkspaceEntity from "../../../entities/WorkspaceEntity" */ export default function Workspace(workspaceEntity) { return ( + + @@ -34,5 +37,7 @@ export default function Workspace(workspaceEntity) { + + ) } diff --git a/src/providers/StatesProvider.jsx b/src/providers/StatesProvider.jsx new file mode 100644 index 0000000..0df3682 --- /dev/null +++ b/src/providers/StatesProvider.jsx @@ -0,0 +1,27 @@ +import { createContext, useState } from "react"; + + +export const StatesContext = createContext(null) + +export function StatesProvider({ children }) { + const [tableState, setTableState] = useState(0) + const [diagramState, setDiagramState] = useState(0) + const [modificationState, setModificationState] = useState(0) + + const updateTable = () => setTableState(prev => prev + 1) + const updateDiagram = () => setDiagramState(prev => prev + 1) + const updateModification = () => setModificationState(prev => prev + 1) + + return ( + + {children} + + ) + +} \ No newline at end of file diff --git a/src/providers/WorkspaceProvider.jsx b/src/providers/WorkspaceProvider.jsx new file mode 100644 index 0000000..a4f6ca7 --- /dev/null +++ b/src/providers/WorkspaceProvider.jsx @@ -0,0 +1,17 @@ +import { createContext, useState } from "react"; +import WorkspaceEntity from "../entities/WorkspaceEntity"; + + +export const WorkspaceContext = createContext(null) + +export function WorkspaceProvider({ children }) { + const emptyWorkspace = new WorkspaceEntity() + const [workspace, setWorkspace] = useState(emptyWorkspace) + + return ( + + {children} + + ) + +} \ No newline at end of file From d4680af13c8163ec08d1b873d31034bee6202a7b Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 30 Sep 2024 15:08:15 +0200 Subject: [PATCH 31/65] Add transform columns and ElvaluationService --- src/entities/DataEntity.js | 30 ++++- src/services/EvaluationService.js | 149 +++++++++++++++++++++++ src/utils/DataUtils.js | 8 ++ tests/services/EvaluationService.test.js | 12 ++ 4 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 src/services/EvaluationService.js create mode 100644 tests/services/EvaluationService.test.js diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 2171af7..f5a6b1c 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,5 +1,6 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { InvalidDataInputError, QuestionnaireTypeError } from "../exceptions/DataExceptions" +import evaluate from "../services/EvaluationService" import { generateEmptyRow, isQuestionColumn } from "../utils/DataUtils" @@ -17,7 +18,7 @@ export default class DataEntity { constructor(type=QUESTIONNAIRE_TYPE.NONE, data=[]) { this.type = type this.data = data - this.columns = { userInfo: [], questions: [] } + this.columns = { userInfo: [], questions: [], transform: [] } if (data.length === 0) { return } this.columns.questions = Object.keys(data[0]).filter(isQuestionColumn) @@ -96,6 +97,27 @@ export default class DataEntity { }) } + addTransformColumns(columns) { + if (typeof columns === "string") { columns = [columns] } + columns = columns.filter(col => !this.transformColumns.includes(col)) + + this.columns.transform = this.columns.transform.concat(columns) + columns.forEach(col => { + const results = evaluate(col, this) + this.data.forEach((row, index) => row[col] = results[index]) + }) + } + + deleteColumns(columns) { + if (typeof columns === "string") { columns = [columns] } + + this.columns.userInfo = this.columns.userInfo.filter(col => !columns.includes(col)) + this.columns.transform = this.columns.transform.filter(col => !columns.includes(col)) + columns.forEach(col => { + this.data.forEach(row => delete row[col]) + }) + } + /************************* @@ -189,16 +211,20 @@ export default class DataEntity { get numOfQuestions() { return this.questionColumns.length } - get allColumns() {return this.userInfoColumns.concat(this.questionColumns)} + get allColumns() {return this.userInfoColumns.concat(this.questionColumns, this.transformColumns)} get userInfoColumns() { return this.columns.userInfo} get questionColumns() { return this.columns.questions } + get transformColumns() { return this.columns.transform } + getType() { return this.type.name } row(rowNumber) { return this.data[rowNumber] } + col(colName) { return this.data.map(row => row[colName]) } + loc(rowNr, column) { return this.data[rowNr][column] } } diff --git a/src/services/EvaluationService.js b/src/services/EvaluationService.js new file mode 100644 index 0000000..19f7d8d --- /dev/null +++ b/src/services/EvaluationService.js @@ -0,0 +1,149 @@ + + + +export function evaluate(expr) { + return parseExpression(tokenize(expr)) +} + + + +/** + * Token parser + */ + +const peek = (tokens) => tokens[0] || null +const consume = (tokens) => tokens.shift() + +function parseExpression(tokens) { + let result = parseTerm(tokens); + while (peek(tokens) && (peek(tokens).value === '+' || peek(tokens).value === '-')) { + const operator = consume(tokens).value; + const right = parseTerm(tokens); + result = (operator === '+') ? result + right : result - right; + } + return result; +} + +function parseTerm(tokens) { + let result = parseFactor(tokens); + while (peek(tokens) && (peek(tokens).value === '*' || peek(tokens).value === '/')) { + const operator = consume(tokens).value; + const right = parseFactor(tokens); + result = (operator === '*') ? result * right : result / right; + } + return result; +} + +function parseFactor(tokens) { + const token = peek(tokens) + if (!token) { + throw new Error(`Unexpected token: ${token ? token.value : 'EOF'}`); + } + + if (token.type === TOKEN.FUNCTION) { + const func = SUPPORTED_FUNCTIONS[consume(tokens).value] + return func(parseFactor(tokens)) + } + + if (token.value === '(') { + consume(tokens) // Consume '(' + const result = parseExpression(tokens); + if (peek(tokens).value === ')') { + consume(tokens) // Consume ')' + } else { + throw new Error("Mismatched parentheses") + } + return result + } + if (token.type === TOKEN.NUMBER) { + return parseFloat(consume(tokens).value) + } + if (token.value === "+" || token.value === "-") { + return 0 + } +} + + + +/** + * Tokenizer + */ + +const TOKEN = Object.freeze({ + NUMBER: "NUMBER", + OPERATOR: "OPERATOR", + PAREN: "PAREN", + FUNCTION: "FUNCTION", + VARIABLE: "VARIABLE" +}) + +const SUPPORTED_FUNCTIONS = { + SCORE: (dataEntity) => 0, + LOG: Math.log +} + +function token(type, value) { return { type: type, value: value } } + +export function tokenize(expr) { + const tokens = [] + let i = 0 + + while (i < expr.length) { + const char = expr[i] + + // Skip whitespace + if (/\s/.test(char)) { + i++ + continue + } + + // Handle numbers + if (/\d/.test(char) || (char === '.' && /\d/.test(expr[i + 1]))) { + let num = char + i++ + while (i < expr.length && /[\d\.]/.test(expr[i])) { + num += expr[i] + i++ + } + + tokens.push(token(TOKEN.NUMBER, num)) + continue + } + + // Handle operators + if (/[+\-*/]/.test(char)) { + tokens.push(token(TOKEN.OPERATOR, char)) + i++ + continue + } + + // Handle parentheses + if (char === "(" || char === ")") { + tokens.push(token(TOKEN.PAREN, char)) + i++ + continue + } + + // Handle functions and variables + if (/[a-zA-Z]/.test(char)) { + let word = "" + while (i < expr.length && /[a-zA-Z]/.test(expr[i])) { + word += expr[i] + i++ + } + + if (word in SUPPORTED_FUNCTIONS) { + tokens.push(token(TOKEN.FUNCTION, word)) + } else { + tokens.push(token(TOKEN.VARIABLE, word)) + } + continue + } + + throw new Error(`Unrecognized character ${char}.`) + + } + + return tokens + +} \ No newline at end of file diff --git a/src/utils/DataUtils.js b/src/utils/DataUtils.js index 636d499..134d32f 100644 --- a/src/utils/DataUtils.js +++ b/src/utils/DataUtils.js @@ -5,6 +5,13 @@ function isQuestionColumn(columnName) { } +function columnType(columnName) { + if (columnName.match(/Q[1-9][0-9]*/g)) { return "questions" } + if (columnName.match(/T[1-9][0-9]*/g)) { return "transform" } + return "userInfo" +} + + /** * Splitting raw questionnaire data object into user info object and result object. * @param {array} rawData @@ -68,6 +75,7 @@ function generateEmptyRow(columns) { export { isQuestionColumn, + columnType, infoResultSplit, generateResultColumns, generateEmptyRow diff --git a/tests/services/EvaluationService.test.js b/tests/services/EvaluationService.test.js new file mode 100644 index 0000000..9ceec4f --- /dev/null +++ b/tests/services/EvaluationService.test.js @@ -0,0 +1,12 @@ +import { test, assert } from "vitest" +import { evaluate, tokenize } from "../../src/services/EvaluationService" + + + +test( + "Tokenizer work properly.", + () => { + console.log(tokenize("LOG(-1.2+.1)")) + console.log(evaluate("LOG(1.71 + 1)")) + } +) From 18acae5d5182446d80b4ffa08b5ce5d817631bbc Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 30 Sep 2024 16:26:59 +0200 Subject: [PATCH 32/65] Complete formula evaluater --- src/entities/DataEntity.js | 8 ++- src/services/EvaluationService.js | 75 ++++++++++++++++-------- src/utils/MathUtils.js | 43 +++++++++++++- tests/services/EvaluationService.test.js | 20 +++++-- 4 files changed, 116 insertions(+), 30 deletions(-) diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index f5a6b1c..786d09d 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -223,7 +223,13 @@ export default class DataEntity { row(rowNumber) { return this.data[rowNumber] } - col(colName) { return this.data.map(row => row[colName]) } + col(colName) { + const allColumns = this.allColumns + if (!allColumns.includes(colName)) { + throw new Error(`${this} does not contain column ${colName}. Available columns are ${allColumns}`) + } + return this.data.map(row => row[colName]) + } loc(rowNr, column) { return this.data[rowNr][column] } diff --git a/src/services/EvaluationService.js b/src/services/EvaluationService.js index 19f7d8d..73b2c5c 100644 --- a/src/services/EvaluationService.js +++ b/src/services/EvaluationService.js @@ -1,53 +1,60 @@ +import { inplaceOperation, apply } from "../utils/MathUtils"; +import dataAnalysisService from "./DataAnalysisService"; -export function evaluate(expr) { - return parseExpression(tokenize(expr)) +export function evaluate(expr, dataEntity) { + return parseExpression(tokenize(expr), dataEntity) } -/** +/*************************** * Token parser - */ + ***************************/ const peek = (tokens) => tokens[0] || null const consume = (tokens) => tokens.shift() -function parseExpression(tokens) { - let result = parseTerm(tokens); +function parseExpression(tokens, dataEntity) { + let result = parseTerm(tokens, dataEntity); while (peek(tokens) && (peek(tokens).value === '+' || peek(tokens).value === '-')) { const operator = consume(tokens).value; - const right = parseTerm(tokens); - result = (operator === '+') ? result + right : result - right; + const right = parseTerm(tokens, dataEntity); + inplaceOperation(result, right, operator)// (operator === '+') ? result + right : result - right; } return result; } -function parseTerm(tokens) { - let result = parseFactor(tokens); +function parseTerm(tokens, dataEntity) { + let result = parseFactor(tokens, dataEntity); while (peek(tokens) && (peek(tokens).value === '*' || peek(tokens).value === '/')) { const operator = consume(tokens).value; - const right = parseFactor(tokens); - result = (operator === '*') ? result * right : result / right; + const right = parseFactor(tokens, dataEntity); + inplaceOperation(result, right, operator) // (operator === '*') ? result * right : result / right; } return result; } -function parseFactor(tokens) { +function parseFactor(tokens, dataEntity) { const token = peek(tokens) if (!token) { - throw new Error(`Unexpected token: ${token ? token.value : 'EOF'}`); + throw new Error("Unexpected token: 'EOF'"); } if (token.type === TOKEN.FUNCTION) { - const func = SUPPORTED_FUNCTIONS[consume(tokens).value] - return func(parseFactor(tokens)) + consume(tokens) + if (token.value === "SCORE") { return FUNCTIONS.SCORE(dataEntity) } + + const func = FUNCTIONS[token.value] // Consume function + const result = parseFactor(tokens, dataEntity) + apply(result, func) + return result } if (token.value === '(') { consume(tokens) // Consume '(' - const result = parseExpression(tokens); + const result = parseExpression(tokens, dataEntity); if (peek(tokens).value === ')') { consume(tokens) // Consume ')' } else { @@ -56,32 +63,44 @@ function parseFactor(tokens) { return result } if (token.type === TOKEN.NUMBER) { - return parseFloat(consume(tokens).value) + consume(tokens) + const num = parseFloat(token.value) + return new Array(dataEntity.size).fill(num) + } + if (token.type === TOKEN.VARIABLE) { + consume(tokens) + return dataEntity.col(token.value) } if (token.value === "+" || token.value === "-") { - return 0 + // Leave + and - for later evaluation. Initialize an array of 0s + return new Array(dataEntity.size).fill(0) } + + throw new Error(`Unexpected token: ${token.value}`); } -/** +/*************************** * Tokenizer - */ + ***************************/ const TOKEN = Object.freeze({ NUMBER: "NUMBER", OPERATOR: "OPERATOR", PAREN: "PAREN", FUNCTION: "FUNCTION", + DATA_FUNC: "DATA_FUNC", VARIABLE: "VARIABLE" }) -const SUPPORTED_FUNCTIONS = { - SCORE: (dataEntity) => 0, +const FUNCTIONS = { + // SCORE function behaves differently. This is accounted for in function parseFactor + SCORE: (dataEntity) => dataAnalysisService.calculateScores(dataEntity), LOG: Math.log } + function token(type, value) { return { type: type, value: value } } export function tokenize(expr) { @@ -106,6 +125,14 @@ export function tokenize(expr) { i++ } + if (tokens.length >= 1) { + const prevToken = tokens[tokens.length - 1] + if (prevToken.type === TOKEN.FUNCTION || prevToken.type === TOKEN.VARIABLE) { + tokens[tokens.length - 1].value += num + continue + } + } + tokens.push(token(TOKEN.NUMBER, num)) continue } @@ -132,7 +159,7 @@ export function tokenize(expr) { i++ } - if (word in SUPPORTED_FUNCTIONS) { + if (word in FUNCTIONS) { tokens.push(token(TOKEN.FUNCTION, word)) } else { tokens.push(token(TOKEN.VARIABLE, word)) diff --git a/src/utils/MathUtils.js b/src/utils/MathUtils.js index 1a28dcd..9003f52 100644 --- a/src/utils/MathUtils.js +++ b/src/utils/MathUtils.js @@ -8,6 +8,47 @@ function average(arr) { } +/** + * @param {number[]} arr1 + * @param {number[] | number} arr2 + * @param {string} operator + * @returns + */ +function inplaceOperation(arr1, arr2, operator) { + + let operation = (a, b) => { + if (operator === "+") { return a + b } + if (operator === "-") { return a - b } + if (operator === "*") { return a * b } + if (operator === "/") { return a / b } + } + + + if (typeof arr2 === "number") { + for (let i = 0; i < arr1.length; i++) { + arr1[i] = operation(arr1[i], arr2) + } + return + } + + if (arr1.length !== arr2.length) { + throw new Error("Function `addTo` only adds two arrays of the same length.") + } + for (let i = 0; i < arr1.length; i++) { + arr1[i] = operation(arr1[i], arr2[i]) + } +} + + +function apply(arr, func) { + for (let i = 0; i < arr.length; i++) { + arr[i] = func(arr[i]) + } +} + + export { - average + average, + inplaceOperation, + apply } \ No newline at end of file diff --git a/tests/services/EvaluationService.test.js b/tests/services/EvaluationService.test.js index 9ceec4f..9cda8d5 100644 --- a/tests/services/EvaluationService.test.js +++ b/tests/services/EvaluationService.test.js @@ -1,12 +1,24 @@ -import { test, assert } from "vitest" +import { test, assert, expect } from "vitest" import { evaluate, tokenize } from "../../src/services/EvaluationService" +import { arraysEqual, generateDataEntity } from "../TestUtils" +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" test( - "Tokenizer work properly.", + "Evaluation function works properly.", () => { - console.log(tokenize("LOG(-1.2+.1)")) - console.log(evaluate("LOG(1.71 + 1)")) + const dataEntity = generateDataEntity( + QUESTIONNAIRE_TYPE.SUS, + [ + [3,3,3,3,3,3,3,3,3,3], + [5,5,5,5,5,5,5,5,5,5], + [3,3,3,3,3,3,3,3,3,3], + ] + ) + assert(arraysEqual(evaluate("SCORE + 1", dataEntity), [51, 51, 51])) + assert(arraysEqual(evaluate("-Q1*2 +1", dataEntity), [-5, -9, -5])) + assert(arraysEqual(evaluate(".2*4+Q3", dataEntity), [3.8, 5.8, 3.8])) + expect(() => evaluate("0??+Q6")).toThrowError() } ) From 448f46825dc8b0e578b18c45473560ef537c8628 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Tue, 1 Oct 2024 09:51:14 +0200 Subject: [PATCH 33/65] Add tests on evaluater --- src/entities/DataEntity.js | 2 +- .../Evaluation.js} | 38 ++++++++++------- tests/entities/DataEntity.test.js | 6 ++- tests/services/EvaluationService.test.js | 24 ----------- tests/utils/Evaluation.test.js | 42 +++++++++++++++++++ 5 files changed, 71 insertions(+), 41 deletions(-) rename src/{services/EvaluationService.js => utils/Evaluation.js} (84%) delete mode 100644 tests/services/EvaluationService.test.js create mode 100644 tests/utils/Evaluation.test.js diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 786d09d..0d7c0de 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,6 +1,6 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { InvalidDataInputError, QuestionnaireTypeError } from "../exceptions/DataExceptions" -import evaluate from "../services/EvaluationService" +import { evaluate } from "../utils/Evaluation" import { generateEmptyRow, isQuestionColumn } from "../utils/DataUtils" diff --git a/src/services/EvaluationService.js b/src/utils/Evaluation.js similarity index 84% rename from src/services/EvaluationService.js rename to src/utils/Evaluation.js index 73b2c5c..1244786 100644 --- a/src/services/EvaluationService.js +++ b/src/utils/Evaluation.js @@ -1,5 +1,5 @@ -import { inplaceOperation, apply } from "../utils/MathUtils"; -import dataAnalysisService from "./DataAnalysisService"; +import { inplaceOperation, apply, average } from "./MathUtils"; +import dataAnalysisService from "../services/DataAnalysisService"; @@ -7,6 +7,20 @@ export function evaluate(expr, dataEntity) { return parseExpression(tokenize(expr), dataEntity) } +/** + * Supported functions. All functions apart from `SCORE` take in an array and + * fill in the results inplace. + */ +const FUNCTIONS = { + // SCORE function behaves differently. This is accounted for in function parseFactor + SCORE: (dataEntity) => dataAnalysisService.calculateScores(dataEntity), + AVG: (arr) => arr.fill(average(arr)), + + ABS: (arr) => apply(arr, Math.abs), + LOG: (arr) => apply(arr, Math.log), + SIN: (arr) => apply(arr, Math.sin), + COS: (arr) => apply(arr, Math.cos), +} /*************************** @@ -21,7 +35,7 @@ function parseExpression(tokens, dataEntity) { while (peek(tokens) && (peek(tokens).value === '+' || peek(tokens).value === '-')) { const operator = consume(tokens).value; const right = parseTerm(tokens, dataEntity); - inplaceOperation(result, right, operator)// (operator === '+') ? result + right : result - right; + inplaceOperation(result, right, operator) } return result; } @@ -31,7 +45,7 @@ function parseTerm(tokens, dataEntity) { while (peek(tokens) && (peek(tokens).value === '*' || peek(tokens).value === '/')) { const operator = consume(tokens).value; const right = parseFactor(tokens, dataEntity); - inplaceOperation(result, right, operator) // (operator === '*') ? result * right : result / right; + inplaceOperation(result, right, operator) } return result; } @@ -43,12 +57,12 @@ function parseFactor(tokens, dataEntity) { } if (token.type === TOKEN.FUNCTION) { - consume(tokens) + consume(tokens) // Consume function if (token.value === "SCORE") { return FUNCTIONS.SCORE(dataEntity) } - const func = FUNCTIONS[token.value] // Consume function + const func = FUNCTIONS[token.value] const result = parseFactor(tokens, dataEntity) - apply(result, func) + func(result) return result } @@ -63,12 +77,12 @@ function parseFactor(tokens, dataEntity) { return result } if (token.type === TOKEN.NUMBER) { - consume(tokens) + consume(tokens) // Consume number const num = parseFloat(token.value) return new Array(dataEntity.size).fill(num) } if (token.type === TOKEN.VARIABLE) { - consume(tokens) + consume(tokens) // Consume variable return dataEntity.col(token.value) } if (token.value === "+" || token.value === "-") { @@ -94,11 +108,7 @@ const TOKEN = Object.freeze({ VARIABLE: "VARIABLE" }) -const FUNCTIONS = { - // SCORE function behaves differently. This is accounted for in function parseFactor - SCORE: (dataEntity) => dataAnalysisService.calculateScores(dataEntity), - LOG: Math.log -} + function token(type, value) { return { type: type, value: value } } diff --git a/tests/entities/DataEntity.test.js b/tests/entities/DataEntity.test.js index 70c6320..cf995d5 100644 --- a/tests/entities/DataEntity.test.js +++ b/tests/entities/DataEntity.test.js @@ -19,11 +19,13 @@ test( ) test( - "New user info columns can be added to DataEntity.", + "New user info columns can be added to or deleted from DataEntity.", () => { noneData.addUserInfoColumns("id") assert(arraysEqual(noneData.userInfoColumns, ["id"])) - noneData.addUserInfoColumns(["id", "age", "gender"]) + noneData.addUserInfoColumns(["id", "age", "gender", "height"]) + assert(arraysEqual(noneData.userInfoColumns, ["id", "age", "gender", "height"])) + noneData.deleteColumns(["height"]) assert(arraysEqual(noneData.userInfoColumns, ["id", "age", "gender"])) } ) diff --git a/tests/services/EvaluationService.test.js b/tests/services/EvaluationService.test.js deleted file mode 100644 index 9cda8d5..0000000 --- a/tests/services/EvaluationService.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { test, assert, expect } from "vitest" -import { evaluate, tokenize } from "../../src/services/EvaluationService" -import { arraysEqual, generateDataEntity } from "../TestUtils" -import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" - - - -test( - "Evaluation function works properly.", - () => { - const dataEntity = generateDataEntity( - QUESTIONNAIRE_TYPE.SUS, - [ - [3,3,3,3,3,3,3,3,3,3], - [5,5,5,5,5,5,5,5,5,5], - [3,3,3,3,3,3,3,3,3,3], - ] - ) - assert(arraysEqual(evaluate("SCORE + 1", dataEntity), [51, 51, 51])) - assert(arraysEqual(evaluate("-Q1*2 +1", dataEntity), [-5, -9, -5])) - assert(arraysEqual(evaluate(".2*4+Q3", dataEntity), [3.8, 5.8, 3.8])) - expect(() => evaluate("0??+Q6")).toThrowError() - } -) diff --git a/tests/utils/Evaluation.test.js b/tests/utils/Evaluation.test.js new file mode 100644 index 0000000..e1aace9 --- /dev/null +++ b/tests/utils/Evaluation.test.js @@ -0,0 +1,42 @@ +import { test, assert, expect } from "vitest" +import { evaluate, tokenize } from "../../src/utils/Evaluation" +import { arraysEqual, generateDataEntity } from "../TestUtils" +import QUESTIONNAIRE_TYPE from "../../src/constants/QuestionnaireType" + + +const dataEntity = generateDataEntity( + QUESTIONNAIRE_TYPE.SUS, + [ + [3,3,3,3,3,3,3,3,3,3], + [5,5,5,5,5,5,5,5,5,5], + [3,3,3,3,3,3,3,3,3,3], + [5,5,5,5,5,5,5,5,5,5] + ] +) + +test( + "Evaluation function works properly.", + () => { + assert(arraysEqual(evaluate("SCORE + 1", dataEntity), [51, 51, 51, 51])) + assert(arraysEqual(evaluate("-Q1*2 +1", dataEntity), [-5, -9, -5, -9])) + assert(arraysEqual(evaluate(".2*4", dataEntity), [.8, .8, .8, .8])) + expect(() => evaluate("0??+Q6", dataEntity)).toThrowError() + } +) + +test( + "Statistic functions works properly.", + () => { + assert(arraysEqual(evaluate("AVG(Q1)", dataEntity), [4, 4, 4, 4])) + } +) + +test( + "DataEntity uses evaluation properly,", + () => { + const expr = "Q1*2+1" + dataEntity.addTransformColumns(expr) + assert(arraysEqual(dataEntity.transformColumns, [expr])) + assert(arraysEqual(dataEntity.col(expr), [7, 11, 7, 11])) + } +) \ No newline at end of file From 1a2bdcaf8ad9aa039e322e37035288bae064e718 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Tue, 1 Oct 2024 18:04:29 +0200 Subject: [PATCH 34/65] Complete transform column --- docs/tutorials/tutorial-entities.md | 56 +++++++++++++++++------------ src/entities/DataEntity.js | 30 +++++++++++----- src/utils/DataUtils.js | 8 ++--- tests/utils/Evaluation.test.js | 7 ++-- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/docs/tutorials/tutorial-entities.md b/docs/tutorials/tutorial-entities.md index ad2d255..a0cf73b 100644 --- a/docs/tutorials/tutorial-entities.md +++ b/docs/tutorials/tutorial-entities.md @@ -15,31 +15,38 @@ DataEntity { scoreInterpretor: [Function: scoreInterpretor] }, data: - [ - { Q1: 11, Q2: 18, Q3: 18, Q4: 17, Q5: 19, Q6: 9, id: 1 }, - { Q1: 13, Q2: 2, Q3: 10, Q4: 14, Q5: 17, Q6: 18, id: 2 } - ], + [ + { Q1: 11, Q2: 18, Q3: 18, Q4: 17, Q5: 19, Q6: 9, id: 1, age: 33, 'T:Q1+Q2': 29 }, + { Q1: 13, Q2: 2, Q3: 10, Q4: 14, Q5: 17, Q6: 18, id: 2, age: 21, 'T:Q1+Q2': 15 } + ], columns: { - userInfo: [ 'id' ], + userInfo: [ 'id', 'age' ], questions: [ 'Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6', 'Q7', 'Q8' - ] + ], + transform: [ 'T:Q1+Q2' ] } } ``` +- `type`: The type of questionnaire. +- `data`: The raw data, represented by an array of objects. When performing data manipulation on the data, **do not** access the raw data like `dataEntity.data[row]["col"]`, **you are required to use the appropriate member functions instead**. +- `columns`: Stores information about columns. + ### Constructor The constructor of `DataEntity` takes in questionnaire type and data, both are optional. The constructor automatically detect questionnaire columns and user info columns. ```javascript -const dataEntity = new DataEntity(QUESTIONNAIRE_TYPE.NONE, - [ - { Q1: 5, Q2: 5, Q3: 5, id: 1 }, - ] - ) +const dataEntity = new DataEntity( + QUESTIONNAIRE_TYPE.NONE, + [ + { Q1: 5, Q2: 5, Q3: 5, id: 1, 'T:Q1+Q3': 10 }, + { Q1: 4, Q2: 3, Q3: 5, id: 2, 'T:Q1+Q3': 7 }, + ] + ) console.log(dataEntity) // Result: // @@ -54,11 +61,13 @@ console.log(dataEntity) // }, // data: // [ -// { Q1: 5, Q2: 5, Q3: 5, id: 1 } +// { Q1: 5, Q2: 5, Q3: 5, id: 1 }, +// { Q1: 4, Q2: 3, Q3: 5, id: 2 }, // ], // columns: { // userInfo: [ 'id' ], -// questions: [ 'Q1', 'Q2', 'Q3' ] +// questions: [ 'Q1', 'Q2', 'Q3' ], +// transform: [ 'T:Q1+Q3' ] // } // } @@ -71,7 +80,8 @@ console.log(dataEntity) | addQuestions(numOfQuestions=1) | Add given number of new questions to the questionnaire. | Only NONE-type data allows adding new questions. | | deleteQuestions(numOfQuestions=1) | Delete given number of new questions to the questionnaire. | Only NONE-type data allows setting number of questions | | addUserInfoColumns(columns) | Given a string or an array of strings, add new strings among them as user info columns. | Existing columns will not be added again. | -| deleteUserInfoColumns(columns) | Given a string or an array of strings, delete user info columns of those names. | | +| addTransformColumns(columns) | Given a string or an array of strings, create transform columns with those name and fill those columns with the transformed data. | Do not include `'T:` in the input. | +| deleteColumns(columns) | Given a string or an array of strings, delete columns with those names. | Only works for `userInfo` and `transform` columns. | ### Row operations @@ -82,10 +92,10 @@ console.log(dataEntity) ### Data manipulation -| Function | Description | -| ------------------------------ | ------------------------------------------------- | -| setValue(rowNr, column, value) | Set value at the given and row number and column. | -| setType(type) | Set questionnaire type. | +| Function | Description | Notes | +| ------------------------------ | ------------------------------------------------- | ------------------------------------------------- | +| setValue(rowNr, column, value) | Set value at the given and row number and column. | Data in transform columns cannot be manually set. | +| setType(type) | Set questionnaire type. | | ### Getters @@ -98,6 +108,7 @@ console.log(dataEntity) | get questionColumns() | Get a list of question columns. | | getType() | Get a string of questionnaire type name. | | row(rowNumber) | Get a single row of data. | +| col(columnName) | Get data from a column as an array. | | loc(rowNr, column) | Get the value at the given row number and column. | ### Example @@ -135,13 +146,14 @@ console.log(dataEntity) // }, // data: // [ -// { Q1: 5, Q2: 5, Q3: 5, Q4: null, id: 1, age: null, gender: null }, -// { Q1: null, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, -// { Q1: 1000, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, +// { Q1: 5, Q2: 5, Q3: 5, Q4: null, id: 1, age: null, gender: null }, +// { Q1: null, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, +// { Q1: 1000, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, // ], // columns: { // userInfo: [ 'id', 'age', 'gender' ], -// questions: [ 'Q1', 'Q2', 'Q3', 'Q4' ] +// questions: [ 'Q1', 'Q2', 'Q3', 'Q4' ], +// transform: [] // } // } ``` diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 0d7c0de..fb0b976 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -1,7 +1,7 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import { InvalidDataInputError, QuestionnaireTypeError } from "../exceptions/DataExceptions" import { evaluate } from "../utils/Evaluation" -import { generateEmptyRow, isQuestionColumn } from "../utils/DataUtils" +import { columnType, generateEmptyRow } from "../utils/DataUtils" /** @@ -21,8 +21,9 @@ export default class DataEntity { this.columns = { userInfo: [], questions: [], transform: [] } if (data.length === 0) { return } - this.columns.questions = Object.keys(data[0]).filter(isQuestionColumn) - this.columns.userInfo = Object.keys(data[0]).filter(k => !isQuestionColumn(k)) + this.columns.questions = Object.keys(data[0]).filter(k => columnType(k) === "questions") + this.columns.userInfo = Object.keys(data[0]).filter(k => columnType(k) === "userInfo") + this.columns.transform = Object.keys(data[0]).filter(k => columnType(k) === "transform") } @@ -85,7 +86,7 @@ export default class DataEntity { } /** - * Given a string or an array of strings, delete user info columns of those names. + * Given a string or an array of strings, delete user info columns with those names. * @param {string | string[]} columns */ deleteUserInfoColumns(columns) { @@ -97,17 +98,26 @@ export default class DataEntity { }) } + /** + * Given a string or an array of strings, create transform columns with those name and + * fill those columns with the transformed data. + * @param {string | string[]} columns + */ addTransformColumns(columns) { if (typeof columns === "string") { columns = [columns] } columns = columns.filter(col => !this.transformColumns.includes(col)) - this.columns.transform = this.columns.transform.concat(columns) + this.columns.transform = this.columns.transform.concat(columns.map(col => `T:${col}`)) columns.forEach(col => { const results = evaluate(col, this) - this.data.forEach((row, index) => row[col] = results[index]) + this.data.forEach((row, index) => row[`T:${col}`] = results[index]) }) } + /** + * Given a string or an array of strings, delete columns with those names. + * @param {string | string[]} columns + */ deleteColumns(columns) { if (typeof columns === "string") { columns = [columns] } @@ -190,11 +200,15 @@ export default class DataEntity { * @param {number} value */ setValue(rowNr, column, value) { - if (isQuestionColumn(column)) { + const colType = columnType(column) + if (colType === "questions") { const questionNr = parseInt(column.substring(1)) this.setResultValue(rowNr, questionNr, value) } - else { this.setUserInfoValue(rowNr, column, value) } + else if (colType === "userInfo") { this.setUserInfoValue(rowNr, column, value) } + else { + throw new InvalidDataInputError("Data in transform columns cannot be manually changed.") + } } setType(type) { diff --git a/src/utils/DataUtils.js b/src/utils/DataUtils.js index 134d32f..59d256d 100644 --- a/src/utils/DataUtils.js +++ b/src/utils/DataUtils.js @@ -1,13 +1,10 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" -function isQuestionColumn(columnName) { - return columnName.match(/Q[1-9][0-9]*/g) -} function columnType(columnName) { - if (columnName.match(/Q[1-9][0-9]*/g)) { return "questions" } - if (columnName.match(/T[1-9][0-9]*/g)) { return "transform" } + if (columnName.match(/^Q[1-9][0-9]*/g)) { return "questions" } + if (columnName.match(/^T:*/g)) { return "transform" } return "userInfo" } @@ -74,7 +71,6 @@ function generateEmptyRow(columns) { export { - isQuestionColumn, columnType, infoResultSplit, generateResultColumns, diff --git a/tests/utils/Evaluation.test.js b/tests/utils/Evaluation.test.js index e1aace9..00ee369 100644 --- a/tests/utils/Evaluation.test.js +++ b/tests/utils/Evaluation.test.js @@ -18,7 +18,7 @@ test( "Evaluation function works properly.", () => { assert(arraysEqual(evaluate("SCORE + 1", dataEntity), [51, 51, 51, 51])) - assert(arraysEqual(evaluate("-Q1*2 +1", dataEntity), [-5, -9, -5, -9])) + assert(arraysEqual(evaluate("(-1+(-Q1+1)*2+ 1)+1", dataEntity), [-3, -7, -3, -7])) assert(arraysEqual(evaluate(".2*4", dataEntity), [.8, .8, .8, .8])) expect(() => evaluate("0??+Q6", dataEntity)).toThrowError() } @@ -36,7 +36,8 @@ test( () => { const expr = "Q1*2+1" dataEntity.addTransformColumns(expr) - assert(arraysEqual(dataEntity.transformColumns, [expr])) - assert(arraysEqual(dataEntity.col(expr), [7, 11, 7, 11])) + console.log(dataEntity) + assert(arraysEqual(dataEntity.transformColumns, [`T:${expr}`])) + assert(arraysEqual(dataEntity.col(`T:${expr}`), [7, 11, 7, 11])) } ) \ No newline at end of file From 79c4b37126a7f7e428fbc453f75b39c17fd1bb73 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Tue, 1 Oct 2024 18:48:48 +0200 Subject: [PATCH 35/65] Fix bug --- docs/tutorials/tutorial-entities.md | 50 ++++++++++++++--------------- src/entities/DataEntity.js | 15 +++++---- tests/utils/Evaluation.test.js | 1 - 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/tutorials/tutorial-entities.md b/docs/tutorials/tutorial-entities.md index a0cf73b..c87e503 100644 --- a/docs/tutorials/tutorial-entities.md +++ b/docs/tutorials/tutorial-entities.md @@ -16,8 +16,8 @@ DataEntity { }, data: [ - { Q1: 11, Q2: 18, Q3: 18, Q4: 17, Q5: 19, Q6: 9, id: 1, age: 33, 'T:Q1+Q2': 29 }, - { Q1: 13, Q2: 2, Q3: 10, Q4: 14, Q5: 17, Q6: 18, id: 2, age: 21, 'T:Q1+Q2': 15 } + { Q1: 11, Q2: 18, Q3: 18, Q4: 17, Q5: 19, Q6: 9, id: 1, age: 33, 'T:Q1+Q2': 29 }, + { Q1: 13, Q2: 2, Q3: 10, Q4: 14, Q5: 17, Q6: 18, id: 2, age: 21, 'T:Q1+Q2': 15 } ], columns: { userInfo: [ 'id', 'age' ], @@ -43,15 +43,15 @@ The constructor of `DataEntity` takes in questionnaire type and data, both are o const dataEntity = new DataEntity( QUESTIONNAIRE_TYPE.NONE, [ - { Q1: 5, Q2: 5, Q3: 5, id: 1, 'T:Q1+Q3': 10 }, - { Q1: 4, Q2: 3, Q3: 5, id: 2, 'T:Q1+Q3': 7 }, + { Q1: 5, Q2: 5, id: 1, 'T:Q1+Q2': 10 }, + { Q1: 4, Q2: 3, id: 2, 'T:Q1+Q2': 7 }, ] ) console.log(dataEntity) // Result: // // DataEntity { -// type: { +// type: { // name: 'NONE', // numOfQuestions: 9007199254740991, // minValue: -9007199254740991, @@ -60,14 +60,14 @@ console.log(dataEntity) // scoreInterpretor: [Function: scoreInterpretor] // }, // data: -// [ -// { Q1: 5, Q2: 5, Q3: 5, id: 1 }, -// { Q1: 4, Q2: 3, Q3: 5, id: 2 }, -// ], +// [ +// { Q1: 5, Q2: 5, id: 1, 'T:Q1+Q2': 10 }, +// { Q1: 4, Q2: 3, id: 2, 'T:Q1+Q2': 7 }, +// ], // columns: { // userInfo: [ 'id' ], -// questions: [ 'Q1', 'Q2', 'Q3' ], -// transform: [ 'T:Q1+Q3' ] +// questions: [ 'Q1', 'Q2' ], +// transform: [ 'T:Q1+Q2' ] // } // } @@ -80,7 +80,7 @@ console.log(dataEntity) | addQuestions(numOfQuestions=1) | Add given number of new questions to the questionnaire. | Only NONE-type data allows adding new questions. | | deleteQuestions(numOfQuestions=1) | Delete given number of new questions to the questionnaire. | Only NONE-type data allows setting number of questions | | addUserInfoColumns(columns) | Given a string or an array of strings, add new strings among them as user info columns. | Existing columns will not be added again. | -| addTransformColumns(columns) | Given a string or an array of strings, create transform columns with those name and fill those columns with the transformed data. | Do not include `'T:` in the input. | +| addTransformColumns(expressions) | Given a string or an array of strings, create transform columns with those name and fill those columns with the transformed data. | Do not include `'T:'` in the input. | | deleteColumns(columns) | Given a string or an array of strings, delete columns with those names. | Only works for `userInfo` and `transform` columns. | ### Row operations @@ -119,24 +119,24 @@ Using the same `dataEntity` we created earlier: console.log(dataEntity.getType()) // Result: NONE -dataEntity.addQuestions() +dataEntity.addQuestions() // This add 1 question by default console.log(dataEntity.questionColumns) -// Result: [ 'Q1', 'Q2', 'Q3', 'Q4' ] +// Result: [ 'Q1', 'Q2', 'Q3' ] -dataEntity.addUserInfoColumns(["id", "age"]) +dataEntity.addUserInfoColumns(["id", "age"]) // Existing columns are not added twice dataEntity.addUserInfoColumns("gender") console.log(dataEntity.allColumns) -// Result" [ 'id', 'age', 'gender', 'Q1', 'Q2', 'Q3', 'Q4' ] +// Result" [ 'Q1', 'Q2', 'Q3', 'id', 'age', 'gender', 'T:Q1+Q2' ] dataEntity.addEmptyRows(2) dataEntity.setValue(2, "Q1", 1000) -console.log(dataEntity.loc(2, "Q1")) +console.log(dataEntity.loc(2, "Q1")) // Get value at the 3rd row in column Q1 // Result: 1000 console.log(dataEntity) // Result: // // DataEntity { -// type: { +// type: { // name: 'NONE', // numOfQuestions: 9007199254740991, // minValue: -9007199254740991, @@ -145,15 +145,15 @@ console.log(dataEntity) // scoreInterpretor: [Function: scoreInterpretor] // }, // data: -// [ -// { Q1: 5, Q2: 5, Q3: 5, Q4: null, id: 1, age: null, gender: null }, -// { Q1: null, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, -// { Q1: 1000, Q2: null, Q3: null, Q4: null, id: null, age: null, gender: null }, -// ], +// [ +// { Q1: 5, Q2: 5, Q3: null, id: 1, age: null, gender: null, 'T:Q1+Q2': 10 }, +// { Q1: 4, Q2: 3, Q3: null, id: null, age: null, gender: null, 'T:Q1+Q2': 7 }, +// { Q1: 1000, Q2: null, Q3: null, id: null, age: null, gender: null, 'T:Q1+Q2': null }, +// ], // columns: { // userInfo: [ 'id', 'age', 'gender' ], -// questions: [ 'Q1', 'Q2', 'Q3', 'Q4' ], -// transform: [] +// questions: [ 'Q1', 'Q2', 'Q3' ], +// transform: [ 'T:Q1+Q2' ] // } // } ``` diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index fb0b976..cce9e8b 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -101,16 +101,17 @@ export default class DataEntity { /** * Given a string or an array of strings, create transform columns with those name and * fill those columns with the transformed data. - * @param {string | string[]} columns + * @param {string | string[]} expressions */ - addTransformColumns(columns) { - if (typeof columns === "string") { columns = [columns] } + addTransformColumns(expressions) { + if (typeof expressions === "string") { expressions = [expressions] } + let columns = expressions.map(col => `T:${col}`) columns = columns.filter(col => !this.transformColumns.includes(col)) - this.columns.transform = this.columns.transform.concat(columns.map(col => `T:${col}`)) - columns.forEach(col => { - const results = evaluate(col, this) - this.data.forEach((row, index) => row[`T:${col}`] = results[index]) + this.columns.transform = this.columns.transform.concat(columns) + expressions.forEach((expr, colIndex) => { + const results = evaluate(expr, this) + this.data.forEach((row, rowNr) => row[columns[colIndex]] = results[rowNr]) }) } diff --git a/tests/utils/Evaluation.test.js b/tests/utils/Evaluation.test.js index 00ee369..3eff9ed 100644 --- a/tests/utils/Evaluation.test.js +++ b/tests/utils/Evaluation.test.js @@ -36,7 +36,6 @@ test( () => { const expr = "Q1*2+1" dataEntity.addTransformColumns(expr) - console.log(dataEntity) assert(arraysEqual(dataEntity.transformColumns, [`T:${expr}`])) assert(arraysEqual(dataEntity.col(`T:${expr}`), [7, 11, 7, 11])) } From edd6efe73e8c780034b33f9ccc2085b836d18930 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Wed, 2 Oct 2024 09:39:05 +0200 Subject: [PATCH 36/65] Preparation for react logic --- src/app/App.jsx | 10 +++++++++- src/constants/DiagramType.js | 7 +++++++ src/entities/DiagramEntity.js | 9 +++++++-- src/entities/WorkspaceEntity.js | 2 +- src/providers/StatesProvider.jsx | 19 ++++++++++++++++--- src/providers/WorkspaceProvider.jsx | 13 ++++++++++--- 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/app/App.jsx b/src/app/App.jsx index 0307ec5..20b6845 100644 --- a/src/app/App.jsx +++ b/src/app/App.jsx @@ -1,3 +1,4 @@ +import { StatesProvider } from '../providers/StatesProvider'; import { WorkspaceProvider } from '../providers/WorkspaceProvider'; import Home from './pages/home/Home' import Workspace from './pages/workspace/Workspace' @@ -10,7 +11,14 @@ function App() { } /> - } /> + + + + } + /> diff --git a/src/constants/DiagramType.js b/src/constants/DiagramType.js index dda9080..fb5c139 100644 --- a/src/constants/DiagramType.js +++ b/src/constants/DiagramType.js @@ -38,6 +38,13 @@ function commonPlotOptions(options) { const DIAGRAM_TYPE = {} +DIAGRAM_TYPE.NONE = { + name: "None", + options: [], + requiredOptions: [], +} + + DIAGRAM_TYPE.HIST = { name: "Histogram", options: ["x"].concat(COMMON_OPTIONS), diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index fa2f401..305dd10 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -1,3 +1,4 @@ +import DIAGRAM_TYPE from "../constants/DiagramType"; import { DiagramTypeError } from "../exceptions/DataExceptions"; import DataEntity from "./DataEntity"; @@ -94,17 +95,21 @@ export default class DiagramEntity { * @returns */ checkPlotability() { + if (this.type === DIAGRAM_TYPE.NONE) { + throw new DiagramTypeError("NONE-type diagram cannot be plotted.") + } for (let i = 0; i < this.type.requiredOptions.length; i++) { let requiredOption = this.type.requiredOptions[i] if (!(requiredOption in this.options)) { - return false + throw new DiagramTypeError(`All required options must be filled. + Required options are ${this.requiredOptions}`) } } return true } - get supportedOptions() { return this.type.options } + get AllOptions() { return this.type.options } get requiredOptions() { return this.type.requiredOptions } diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js index d9056ed..97ad9f1 100644 --- a/src/entities/WorkspaceEntity.js +++ b/src/entities/WorkspaceEntity.js @@ -9,7 +9,7 @@ export default class WorkspaceEntity { this.dataEntity = dataEntity } - setDiagramEntity() { + setDiagramEntity(diagramEntity) { this.diagramEntity = diagramEntity } diff --git a/src/providers/StatesProvider.jsx b/src/providers/StatesProvider.jsx index 0df3682..ba465ef 100644 --- a/src/providers/StatesProvider.jsx +++ b/src/providers/StatesProvider.jsx @@ -1,7 +1,7 @@ -import { createContext, useState } from "react"; +import { createContext, useContext, useState } from "react"; -export const StatesContext = createContext(null) +const StatesContext = createContext(null) export function StatesProvider({ children }) { const [tableState, setTableState] = useState(0) @@ -11,17 +11,30 @@ export function StatesProvider({ children }) { const updateTable = () => setTableState(prev => prev + 1) const updateDiagram = () => setDiagramState(prev => prev + 1) const updateModification = () => setModificationState(prev => prev + 1) + const updateAll = () => { + updateTable() + updateDiagram() + updateModification() + } return ( {children} ) +} +export function useStatesContext() { + const statesContext = useContext(StatesContext); + if (!statesContext) { + throw new Error("StatesContext must be used within a ContextProvider") + } + return statesContext } \ No newline at end of file diff --git a/src/providers/WorkspaceProvider.jsx b/src/providers/WorkspaceProvider.jsx index a4f6ca7..7cb9896 100644 --- a/src/providers/WorkspaceProvider.jsx +++ b/src/providers/WorkspaceProvider.jsx @@ -1,17 +1,24 @@ -import { createContext, useState } from "react"; +import { createContext, useContext, useState } from "react"; import WorkspaceEntity from "../entities/WorkspaceEntity"; -export const WorkspaceContext = createContext(null) +const WorkspaceContext = createContext(null) export function WorkspaceProvider({ children }) { const emptyWorkspace = new WorkspaceEntity() const [workspace, setWorkspace] = useState(emptyWorkspace) - + return ( {children} ) +} +export function useWorkspaceContext() { + const workspaceContext = useContext(WorkspaceContext); + if (!workspaceContext) { + throw new Error("WorkspaceContext must be used within a ContextProvider") + } + return workspaceContext } \ No newline at end of file From 79c93c336e5aec2a4f4d3423f5063738216ab581 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Wed, 2 Oct 2024 09:53:41 +0200 Subject: [PATCH 37/65] Connect context providers to components --- src/app/pages/workspace/Workspace.jsx | 31 +++++++++++++------ .../modification/ModSubcomponents.jsx | 0 .../workspace/modification/Modification.jsx | 16 ++++++++-- .../workspace/visualization/Visualization.jsx | 3 +- src/components/Diagram.jsx | 22 +++++++++---- 5 files changed, 52 insertions(+), 20 deletions(-) delete mode 100644 src/app/pages/workspace/modification/ModSubcomponents.jsx diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx index c815eb7..7344b8b 100644 --- a/src/app/pages/workspace/Workspace.jsx +++ b/src/app/pages/workspace/Workspace.jsx @@ -3,20 +3,33 @@ import "./Workspace.css" import DataTable from "./datatables/DataTableTabs" import Visualization from "./visualization/Visualization" import Modification from "./modification/Modification" -import WorkspaceEntity from "../../../entities/WorkspaceEntity" -import { StatesProvider } from "../../../providers/StatesProvider" +import { useStatesContext } from "../../../providers/StatesProvider" +import { useEffect } from "react" +import { useWorkspaceContext } from "../../../providers/WorkspaceProvider" +import dataService from "../../../services/DataService" +import DiagramEntity from "../../../entities/DiagramEntity" +import DIAGRAM_TYPE from "../../../constants/DiagramType" /** * Main user interface for data analysis. Contains `DataTable`, * `Visualization` and `Modification` as sub-components. - * - * @param {WorkspaceEntity} workspaceEntity This argument should be a `WorkspaceEntity`. - * @returns {ReactNode} Rendered React component. */ -export default function Workspace(workspaceEntity) { - return ( - +export default function Workspace() { + const { workspace, setWorkspace } = useWorkspaceContext() + const { updateAll } = useStatesContext() + + // Import test data. Must be deleted in production + useEffect(() => { + dataService.importData("tests/test-data/SUS-example.csv") + .then(dataEntity => { + workspace.setDataEntity(dataEntity) + const diagramEntity = new DiagramEntity(DIAGRAM_TYPE.NONE, dataEntity) + workspace.setDiagramEntity(diagramEntity) + updateAll() + }) + }, []) + return ( @@ -37,7 +50,5 @@ export default function Workspace(workspaceEntity) { - - ) } diff --git a/src/app/pages/workspace/modification/ModSubcomponents.jsx b/src/app/pages/workspace/modification/ModSubcomponents.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index 41a51fe..e0c05ba 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -1,10 +1,20 @@ +import { useStatesContext } from "../../../../providers/StatesProvider" +import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider" /** - * The user interface for modifying diagrams. - * - * @returns {ReactNode} Rendered component. + * The user interface for modifying diagram options. */ function Modification() { + const { modificationState, updateDiagram } = useStatesContext() + const { workspace } = useWorkspaceContext() + + // The diagram entity to be worked on + const diagramEntity = workspace.diagramEntity + if (!diagramEntity) { return <> } + + const allOptions = diagramEntity.allOptions + const requiredOptions = diagramEntity.requiredOptions + return (

Modification

) diff --git a/src/app/pages/workspace/visualization/Visualization.jsx b/src/app/pages/workspace/visualization/Visualization.jsx index 86b50aa..9072fbf 100644 --- a/src/app/pages/workspace/visualization/Visualization.jsx +++ b/src/app/pages/workspace/visualization/Visualization.jsx @@ -1,3 +1,4 @@ +import Diagram from "../../../../components/Diagram"; /** * A component containing diagrams from a workspace. @@ -7,6 +8,6 @@ export default function Visualization() { return ( -

Visualization

+ ); } diff --git a/src/components/Diagram.jsx b/src/components/Diagram.jsx index 4c82b58..9cb30c4 100644 --- a/src/components/Diagram.jsx +++ b/src/components/Diagram.jsx @@ -1,14 +1,24 @@ import * as Plot from "@observablehq/plot"; import Document from "../utils/Document" +import { useWorkspaceContext } from "../providers/WorkspaceProvider"; +import { useStatesContext } from "../providers/StatesProvider"; /** - * - * @param {DiagramEntity} diagramEntity - * @returns + * React component of a d3.js diagram */ -export default function Diagram({ diagramEntity }) { +export default function Diagram() { + const { diagramState } = useStatesContext() + const { workspace } = useWorkspaceContext() + const diagramEntity = workspace.diagramEntity + if (!diagramEntity) { return <> } - const plotOptions = diagramEntity.generatePlotOptions() - return Plot.plot({ ...plotOptions, document: new Document() }).toHyperScript(); + + // diagramEntity.setOption("x", "age") + try { + const plotOptions = diagramEntity.generatePlotOptions() + return Plot.plot({ ...plotOptions, document: new Document() }).toHyperScript(); + } catch (error) { + return

{error.message}

+ } } \ No newline at end of file From ad27bf928504c287b146efc95ef138fc80d77043 Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:53:36 +0200 Subject: [PATCH 38/65] Update DataTableTabs.jsx local correction, no pull --- .../workspace/datatables/DataTableTabs.jsx | 52 +------------------ 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index ddb1270..c9b3b00 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -6,58 +6,8 @@ */ export default function DataTableTabs() { - const [activeTab, setActiveTab] = useState(0); - const [dataTables, setDataTables] = useState([]); - useEffect(() => { - Promise.all(csvFiles.map(file => d3.csv(file))) - .then(data => { - setDataTables(data); - }) - .catch(error => { - console.error('Error', error); - }); - }, []); - return ( -
-
- {dataTables.map((_, index) => ( - - ))} -
- -
- {dataTables.length > 0 ? ( - - - - {Object.keys(dataTables[activeTab][0]).map((columnName, i) => ( - - ))} - - - - {dataTables[activeTab].map((row, i) => ( - - {Object.values(row).map((value, j) => ( - - ))} - - ))} - -
{columnName}
{value}
- ) : ( -

Loading...

- )} -
-
- ); + return; } \ No newline at end of file From 996877d35dda218b9375afa749b46cc3e1b397ac Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Wed, 2 Oct 2024 16:18:10 +0200 Subject: [PATCH 39/65] Design Landing Page --- package-lock.json | 212 ++++++++++++++++++++++-------------- package.json | 5 +- src/app/pages/home/Home.css | 83 +++++++++++++- src/app/pages/home/Home.jsx | 145 ++++++++++++++++++++---- 4 files changed, 337 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c46479..f711e24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,10 @@ "name": "usability-statistic", "version": "0.0.0", "dependencies": { - "@emotion/react": "^11.13.0", + "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/material": "^5.16.6", + "@mui/icons-material": "^6.1.1", + "@mui/material": "^6.1.1", "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -275,9 +276,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -401,14 +403,15 @@ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, "node_modules/@emotion/react": { - "version": "11.13.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz", - "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==", + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.12.0", "@emotion/cache": "^11.13.0", - "@emotion/serialize": "^1.3.0", + "@emotion/serialize": "^1.3.1", "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", "@emotion/utils": "^1.4.0", "@emotion/weak-memoize": "^0.4.0", @@ -424,14 +427,15 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz", - "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", - "@emotion/unitless": "^0.9.0", - "@emotion/utils": "^1.4.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", "csstype": "^3.0.2" } }, @@ -444,6 +448,7 @@ "version": "11.13.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.12.0", @@ -463,9 +468,10 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz", - "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.1.0", @@ -476,9 +482,10 @@ } }, "node_modules/@emotion/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", @@ -1026,34 +1033,62 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz", - "integrity": "sha512-kytg6LheUG42V8H/o/Ptz3olSO5kUXW9zF0ox18VnblX6bO2yif1FPItgc3ey1t5ansb1+gbe7SatntqusQupg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.1.tgz", + "integrity": "sha512-VdQC1tPIIcZAnf62L2M1eQif0x2vlKg3YK4kGYbtijSH4niEgI21GnstykW1vQIs+Bc6L+Hua2GATYVjilJ22A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.1.tgz", + "integrity": "sha512-sy/YKwcLPW8VcacNP2uWMYR9xyWuwO9NN9FXuGEU90bRshBXj8pdKk+joe3TCW7oviVS3zXLHlc94wQ0jNsQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6" + }, + "engines": { + "node": ">=14.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@mui/material": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.6.tgz", - "integrity": "sha512-0LUIKBOIjiFfzzFNxXZBRAyr9UQfmTAFzbt6ziOU2FDXhorNN2o3N9/32mNJbCA8zJo2FqFU6d3dtoqUDyIEfA==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/core-downloads-tracker": "^5.16.6", - "@mui/system": "^5.16.6", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.6", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.1.tgz", + "integrity": "sha512-b+eULldTqtqTCbN++2BtBWCir/1LwEYw+2mIlOt2GiEUh1EBBw4/wIukGKKNt3xrCZqRA80yLLkV6tF61Lq3cA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/core-downloads-tracker": "^6.1.1", + "@mui/system": "^6.1.1", + "@mui/types": "^7.2.17", + "@mui/utils": "^6.1.1", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1062,9 +1097,10 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mui/material-pigment-css": "^6.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1073,6 +1109,9 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, "@types/react": { "optional": true } @@ -1084,24 +1123,25 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/@mui/private-theming": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", - "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz", + "integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.16.6", + "@babel/runtime": "^7.25.6", + "@mui/utils": "^6.1.1", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1110,17 +1150,19 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", - "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz", + "integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", + "@babel/runtime": "^7.25.6", + "@emotion/cache": "^11.13.1", + "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1129,7 +1171,7 @@ "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1141,21 +1183,22 @@ } }, "node_modules/@mui/system": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.6.tgz", - "integrity": "sha512-5xgyJjBIMPw8HIaZpfbGAaFYPwImQn7Nyh+wwKWhvkoIeDosQ1ZMVrbTclefi7G8hNmqhip04duYwYpbBFnBgw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.16.6", - "@mui/styled-engine": "^5.16.6", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.6", - "clsx": "^2.1.0", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz", + "integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/private-theming": "^6.1.1", + "@mui/styled-engine": "^6.1.1", + "@mui/types": "^7.2.17", + "@mui/utils": "^6.1.1", + "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1164,8 +1207,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1180,11 +1223,12 @@ } }, "node_modules/@mui/types": { - "version": "7.2.15", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz", - "integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==", + "version": "7.2.17", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz", + "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==", + "license": "MIT", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1193,27 +1237,28 @@ } }, "node_modules/@mui/utils": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", - "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz", + "integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/types": "^7.2.15", + "@babel/runtime": "^7.25.6", + "@mui/types": "^7.2.17", "@types/prop-types": "^15.7.12", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^18.3.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1224,7 +1269,8 @@ "node_modules/@mui/utils/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -1586,9 +1632,10 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -2117,6 +2164,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index 29f3474..d46bafb 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,10 @@ "test": "vitest" }, "dependencies": { - "@emotion/react": "^11.13.0", + "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/material": "^5.16.6", + "@mui/icons-material": "^6.1.1", + "@mui/material": "^6.1.1", "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/app/pages/home/Home.css b/src/app/pages/home/Home.css index b9d355d..80286d8 100644 --- a/src/app/pages/home/Home.css +++ b/src/app/pages/home/Home.css @@ -1,3 +1,4 @@ +/* Css for Landing Page*/ #root { max-width: 1280px; margin: 0 auto; @@ -7,7 +8,7 @@ .logo { height: 6em; - padding: 1.5em; + /*padding: 1.5em;*/ will-change: filter; transition: filter 300ms; } @@ -33,6 +34,49 @@ } } +.fieldDataFile { + background-color: #f9f9f9d8; + border: 1px solid #ccc; + border-radius: 8px; + padding: 20px; + margin: 5px 0; +} + +.fieldDataFile:hover { + border-color: #000; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); +} + +.button{ + border: 1px solid #ccc; + cursor: pointer; +} + +.button:hover { + border-color: #000; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); +} + + +.buttonColumn { + display: flex; /*Columns*/ + justify-content: center; + margin-top: 1em; +} +.buttonDataFile { + flex: 1; + margin: 0 0.5em; + text-align: center; +} +.buttonDataFile select { + margin: 1em 0; + padding: 0.5em; + width: 50%; +} +.separator { + width: 2px; + margin: 0 1em; +} .card { padding: 2em; } @@ -40,3 +84,40 @@ .read-the-docs { color: #888; } + +.helpIcon{ + position: absolute; + transform: scale(1.8); + left: 600px; +} + +.helpIcon:hover { + color: #000; +} + +.darkmode{ + margin-left: 1200px; +} + +.modal-box { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + padding: 20px; + border-radius: 8px; +} + +.exampleTable { + width: 100%; + border-collapse: collapse; + margin: 10px 0; +} + +.exampleTable th, +.exampleTable td { + border: 1px solid black; + padding: 8px; + text-align: left; +} diff --git a/src/app/pages/home/Home.jsx b/src/app/pages/home/Home.jsx index ea9059f..47e2dde 100644 --- a/src/app/pages/home/Home.jsx +++ b/src/app/pages/home/Home.jsx @@ -3,46 +3,145 @@ import reactLogo from '../../../assets/react.svg' import viteLogo from '/vite.svg' import './Home.css' import { Link } from 'react-router-dom' +import { IconButton, Modal, Box, Typography } from '@mui/material'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; /** - * Main component for home page. + * Main component for home page. Landing Page. * * @returns Rendered react component. */ function Home() { - const [count, setCount] = useState(0) - - return ( - <> + // Control if modal is closed + const [isHelpOpen, setIsHelpOpen] = useState(false); + const [isUserDataFormatOpen, setIsUserDataFormatOpen] = useState(false); + const handleOpenHelp = () => setIsHelpOpen(true); + const handleCloseHelp = () => setIsHelpOpen(false); + const handleOpenUserDataFormat = () => setIsUserDataFormatOpen(true); + const handleCloseUserDataFormat = () => setIsUserDataFormatOpen(false); + + return ( + <> + + + + + {/* Modal for Help-Icon*/} + + +

Hilfe

+

+ Bei dieser Seite handelt es sich, um ein Tool, dass dir dabei hilft deine Usability-Daten auszuwerten. Lade Sie hoch und profitiere von den Auswertungsfunktionen. Falls du Fragen hast Melde dich sehr gerne bei uns. +

+ +
+
+

Darkmode-Reminder

+ {/*Logo*/} -

Vite + React

-
- -

- Edit src/app/pages/home/Home.jsx and save to test HMR +

Usability Analyzer

+

+ Dein Tool, um schnell und unkompliziert Usability-Daten auzuswerten

- - {/* use Link for redirecting */} +
+ {/* Modal for Info UserData*/} +
+

Lade hier die demographischen Daten hoch. Zulässige Daten sind User-ID, Alter und Geschlecht. Für eine Beispielsdatei klicke hier.

+ + +

Datenformat

+

+ Zugelassen sind nur csv-Dateien. Diese müssen für die Analyse im folgenden Format vorliegen. + + + + + + + + + + + + + + + + + + +
125M
230W
322M
+

+ +
+
+ +
+ {/* Choose Usability Data*/} +
+ {/* Upload Usability Data*/} +
+

Hier kannst du deine Daten hochladen. Wähle zuerst den Usabiliyt-Fragebogen aus, welchen du gerne analysieren möchtest. Für Infos zum Datenformat klicke, HIER!

+ +
+
+ +
+
+
+ {/* Upload Usability Data*/} +

Hier kannst du deine Daten maskieren. Wähle zuerst den Usabiliyt-Fragebogen aus, welchen du gerne analysieren möchtest. Für Infos zum Datenformat klicke, HIER!

+ +
+
+ +
+
+
+ {/* use Link for redirecting */} +
- + - -
+

- Click on the Vite and React logos to learn more + Impressum!!!!!!!!!!!!!!!!!!!!!

+ + +{/*

+ + Edit src/app/pages/home/Home.jsx and save to test HMR +

*/} + + ) } From 9387af529df5579780384b25e691be489c898bb7 Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:46:02 +0200 Subject: [PATCH 40/65] DataTable Design --- package-lock.json | 282 ++++++++++-------- package.json | 2 +- .../workspace/datatables/DataTableTabs.jsx | 63 +++- 3 files changed, 225 insertions(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index f711e24..fcdefe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.1", - "@mui/material": "^6.1.1", + "@mui/material": "^6.1.2", "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1033,9 +1033,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.1.tgz", - "integrity": "sha512-VdQC1tPIIcZAnf62L2M1eQif0x2vlKg3YK4kGYbtijSH4niEgI21GnstykW1vQIs+Bc6L+Hua2GATYVjilJ22A==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.2.tgz", + "integrity": "sha512-1oE4U38/TtzLWRYWEm/m70dUbpcvBx0QvDVg6NtpOmSNQC1Mbx0X/rNvYDdZnn8DIsAiVQ+SZ3am6doSswUQ4g==", "license": "MIT", "funding": { "type": "opencollective", @@ -1069,16 +1069,16 @@ } }, "node_modules/@mui/material": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.1.tgz", - "integrity": "sha512-b+eULldTqtqTCbN++2BtBWCir/1LwEYw+2mIlOt2GiEUh1EBBw4/wIukGKKNt3xrCZqRA80yLLkV6tF61Lq3cA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.2.tgz", + "integrity": "sha512-5TtHeAVX9D5d2LYfB1GAUn29BcVETVsrQ76Dwb2SpAfQGW3JVy4deJCAd0RrIkI3eEUrsl0E4xuBdreszxdTTg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.6", - "@mui/core-downloads-tracker": "^6.1.1", - "@mui/system": "^6.1.1", + "@mui/core-downloads-tracker": "^6.1.2", + "@mui/system": "^6.1.2", "@mui/types": "^7.2.17", - "@mui/utils": "^6.1.1", + "@mui/utils": "^6.1.2", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", @@ -1097,7 +1097,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.1", + "@mui/material-pigment-css": "^6.1.2", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1123,13 +1123,13 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/@mui/private-theming": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.1.tgz", - "integrity": "sha512-JlrjIdhyZUtewtdAuUsvi3ZnO0YS49IW4Mfz19ZWTlQ0sDGga6LNPVwHClWr2/zJK2we2BQx9/i8M32rgKuzrg==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.2.tgz", + "integrity": "sha512-S8WcjZdNdi++8UhrrY8Lton5h/suRiQexvdTfdcPAlbajlvgM+kx+uJstuVIEyTb3gMkxzIZep87knZ0tqcR0g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.6", - "@mui/utils": "^6.1.1", + "@mui/utils": "^6.1.2", "prop-types": "^15.8.1" }, "engines": { @@ -1150,9 +1150,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.1.tgz", - "integrity": "sha512-HJyIoMpFb11fnHuRtUILOXgq6vj4LhIlE8maG4SwP/W+E5sa7HFexhnB3vOMT7bKys4UKNxhobC8jwWxYilGsA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.2.tgz", + "integrity": "sha512-uKOfWkR23X39xj7th2nyTcCHqInTAXtUnqD3T5qRVdJcOPvu1rlgTleTwJC/FJvWZJBU6ieuTWDhbcx5SNViHQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.6", @@ -1183,16 +1183,16 @@ } }, "node_modules/@mui/system": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.1.tgz", - "integrity": "sha512-PaYsCz2tUOcpu3T0okDEsSuP/yCDIj9JZ4Tox1JovRSKIjltHpXPsXZSGr3RiWdtM1MTQMFMCZzu0+CKbyy+Kw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.2.tgz", + "integrity": "sha512-mzW7F1ZMIYS1aLON48Nrk9c65OrVEVQ+R4lUcTWs1lCSul0VGK23eo4dmY0NX5PS7Oe4xz3P5B9tQZZ7SYgxcg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.6", - "@mui/private-theming": "^6.1.1", - "@mui/styled-engine": "^6.1.1", + "@mui/private-theming": "^6.1.2", + "@mui/styled-engine": "^6.1.2", "@mui/types": "^7.2.17", - "@mui/utils": "^6.1.1", + "@mui/utils": "^6.1.2", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1237,14 +1237,14 @@ } }, "node_modules/@mui/utils": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.1.tgz", - "integrity": "sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.2.tgz", + "integrity": "sha512-6+B1YZ8cCBWD1fc3RjqpclF9UA0MLUiuXhyCO+XowD/Z2ku5IlxeEhHHlgglyBWFGMu4kib4YU3CDsG5/zVjJQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.6", "@mui/types": "^7.2.17", - "@types/prop-types": "^15.7.12", + "@types/prop-types": "^15.7.13", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^18.3.1" @@ -1311,6 +1311,7 @@ "version": "0.6.16", "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.16.tgz", "integrity": "sha512-LRi9Rn93yUx90MIo2Md7+vazxO3Wiat14but2ttCER0xVS+jnfoUjuCGoz6H7bz/lgI9CFcW0HWlvWjMFjAv8g==", + "license": "ISC", "dependencies": { "d3": "^7.9.0", "interval-tree-1d": "^1.0.0", @@ -1338,208 +1339,224 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1587,10 +1604,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.5.4", @@ -1609,9 +1627,10 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.3", @@ -2024,6 +2043,25 @@ "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4230,6 +4268,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -4467,9 +4506,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -4481,9 +4521,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -4499,10 +4539,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4732,12 +4773,13 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -4747,22 +4789,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, @@ -4937,10 +4979,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -5294,14 +5337,15 @@ } }, "node_modules/vite": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", - "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.40", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index d46bafb..ea63e14 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.1", - "@mui/material": "^6.1.1", + "@mui/material": "^6.1.2", "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index c9b3b00..8a26ce3 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -4,10 +4,69 @@ * * @returns {ReactNode} Rendered tabs component. */ +import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + Paper, +} from "@mui/material"; export default function DataTableTabs() { + const { workspace } = useWorkspaceContext(); + const dataEntity = workspace.dataEntity; - - return; + console.log("Data Entity:", dataEntity); + if (!dataEntity || !dataEntity.allColumns) { + return ( + + No data available + + ); + } + + return ( +
+ + 😊 Deine Daten: + + + + + + {dataEntity.allColumns.map((column) => ( + + {column} + + ))} + + + + {dataEntity.data && dataEntity.data.length > 0 ? ( + dataEntity.data.map((row) => ( + + {dataEntity.allColumns.map((column) => ( + {row[column]} + ))} + + )) + ) : ( + + + + No data available + + + + )} + +
+
+
+ ); } \ No newline at end of file From 33f2332fa67e51da51d656b67e458c6960103acd Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 3 Oct 2024 07:32:28 +0200 Subject: [PATCH 41/65] package-lock backup --- package-lock.json | 175 +++++++++++++++++++++++----------------------- 1 file changed, 87 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index f711e24..70e3ac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1046,7 +1046,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.1.tgz", "integrity": "sha512-sy/YKwcLPW8VcacNP2uWMYR9xyWuwO9NN9FXuGEU90bRshBXj8pdKk+joe3TCW7oviVS3zXLHlc94wQ0jNsQRQ==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.6" }, @@ -1338,9 +1337,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], @@ -1351,9 +1350,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], @@ -1364,9 +1363,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], @@ -1377,9 +1376,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], @@ -1390,9 +1389,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], @@ -1403,9 +1402,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], @@ -1416,9 +1415,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], @@ -1429,9 +1428,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], @@ -1442,9 +1441,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], @@ -1455,9 +1454,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], @@ -1468,9 +1467,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], @@ -1481,9 +1480,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], @@ -1494,9 +1493,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], @@ -1507,9 +1506,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], @@ -1520,9 +1519,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], @@ -1533,9 +1532,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], @@ -1587,9 +1586,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/node": { @@ -4467,9 +4466,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -4481,9 +4480,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -4501,8 +4500,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4732,12 +4731,12 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -4747,22 +4746,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, @@ -4937,9 +4936,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5294,14 +5293,14 @@ } }, "node_modules/vite": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", - "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.40", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" From 7195897ca649426bdb70ac3357e9f6dfa30cbe4a Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 3 Oct 2024 12:41:02 +0200 Subject: [PATCH 42/65] Connect logic to react --- package-lock.json | 148 ++++++++++++++--- package.json | 1 + src/app/pages/home/Home.jsx | 157 +++++++++--------- src/app/pages/workspace/Workspace.css | 6 + src/app/pages/workspace/Workspace.jsx | 20 ++- .../pages/workspace/visualization/Diagram.jsx | 26 +++ .../workspace/visualization/Visualization.jsx | 2 +- src/entities/WorkspaceEntity.js | 3 + 8 files changed, 261 insertions(+), 102 deletions(-) create mode 100644 src/app/pages/workspace/visualization/Diagram.jsx diff --git a/package-lock.json b/package-lock.json index 07eaed9..bab8b86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.1", "@mui/material": "^6.1.2", + "@mui/x-data-grid": "^7.18.0", "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1271,6 +1272,129 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/@mui/x-data-grid": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.18.0.tgz", + "integrity": "sha512-41UjJbRxWk+Yk/lfvaO55Pwo5p+F5s3rOTiHLl53ikCT5GuJ5OCCvik0Bi3c6DzTuUBdrEucae2618rydc2DGw==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/utils": "^5.16.6", + "@mui/x-internals": "7.18.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid/node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@mui/x-internals": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.18.0.tgz", + "integrity": "sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/utils": "^5.16.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/x-internals/node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2042,25 +2166,6 @@ "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==" }, - "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "license": "MIT", - "peerDependencies": { - "@popperjs/core": "^2.11.8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4731,6 +4836,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", diff --git a/package.json b/package.json index ea63e14..7d4ede6 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.1", "@mui/material": "^6.1.2", + "@mui/x-data-grid": "^7.18.0", "@observablehq/plot": "^0.6.16", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/app/pages/home/Home.jsx b/src/app/pages/home/Home.jsx index 47e2dde..b63b41b 100644 --- a/src/app/pages/home/Home.jsx +++ b/src/app/pages/home/Home.jsx @@ -5,6 +5,7 @@ import './Home.css' import { Link } from 'react-router-dom' import { IconButton, Modal, Box, Typography } from '@mui/material'; import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import { useWorkspaceContext } from '../../../providers/WorkspaceProvider' /** @@ -13,32 +14,38 @@ import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; * @returns Rendered react component. */ function Home() { - // Control if modal is closed - const [isHelpOpen, setIsHelpOpen] = useState(false); - const [isUserDataFormatOpen, setIsUserDataFormatOpen] = useState(false); - const handleOpenHelp = () => setIsHelpOpen(true); - const handleCloseHelp = () => setIsHelpOpen(false); - const handleOpenUserDataFormat = () => setIsUserDataFormatOpen(true); - const handleCloseUserDataFormat = () => setIsUserDataFormatOpen(false); - - return ( - <> - - - - - {/* Modal for Help-Icon*/} - - -

Hilfe

-

- Bei dieser Seite handelt es sich, um ein Tool, dass dir dabei hilft deine Usability-Daten auszuwerten. Lade Sie hoch und profitiere von den Auswertungsfunktionen. Falls du Fragen hast Melde dich sehr gerne bei uns. -

- -
-
-

Darkmode-Reminder

- {/*Logo*/} + // ---------- Danger zone starts here ---------- + const { workspace } = useWorkspaceContext() + + // ---------- Danger zone ends here ---------- + + + // Control if modal is closed + const [isHelpOpen, setIsHelpOpen] = useState(false); + const [isUserDataFormatOpen, setIsUserDataFormatOpen] = useState(false); + const handleOpenHelp = () => setIsHelpOpen(true); + const handleCloseHelp = () => setIsHelpOpen(false); + const handleOpenUserDataFormat = () => setIsUserDataFormatOpen(true); + const handleCloseUserDataFormat = () => setIsUserDataFormatOpen(false); + + return ( + <> + + + + + {/* Modal for Help-Icon*/} + + +

Hilfe

+

+ Bei dieser Seite handelt es sich, um ein Tool, dass dir dabei hilft deine Usability-Daten auszuwerten. Lade Sie hoch und profitiere von den Auswertungsfunktionen. Falls du Fragen hast Melde dich sehr gerne bei uns. +

+ +
+
+

Darkmode-Reminder

+ {/*Logo*/}

Usability Analyzer

- Dein Tool, um schnell und unkompliziert Usability-Daten auzuswerten -

+ Dein Tool, um schnell und unkompliziert Usability-Daten auzuswerten +

- {/* Modal for Info UserData*/} + {/* Modal for Info UserData*/}

Lade hier die demographischen Daten hoch. Zulässige Daten sind User-ID, Alter und Geschlecht. Für eine Beispielsdatei klicke hier.

- -

Datenformat

-

- Zugelassen sind nur csv-Dateien. Diese müssen für die Analyse im folgenden Format vorliegen. - - - - - - - - - - - - - - - - - - -
125M
230W
322M
-

- -
-
+ +

Datenformat

+

+ Zugelassen sind nur csv-Dateien. Diese müssen für die Analyse im folgenden Format vorliegen. + + + + + + + + + + + + + + + + + + +
125M
230W
322M
+

+ +
+ + Upload demographic data +
{/* Choose Usability Data*/}
{/* Upload Usability Data*/} -
-

Hier kannst du deine Daten hochladen. Wähle zuerst den Usabiliyt-Fragebogen aus, welchen du gerne analysieren möchtest. Für Infos zum Datenformat klicke, HIER!

- @@ -98,15 +105,15 @@ function Home() {

- -
-
-
- {/* Upload Usability Data*/} -

Hier kannst du deine Daten maskieren. Wähle zuerst den Usabiliyt-Fragebogen aus, welchen du gerne analysieren möchtest. Für Infos zum Datenformat klicke, HIER!

- @@ -115,11 +122,11 @@ function Home() {

- -
-
+ +
+ {/* use Link for redirecting */}
@@ -128,13 +135,13 @@ function Home() { Start to Analyse -
+

Impressum!!!!!!!!!!!!!!!!!!!!!

-{/*

diff --git a/src/app/pages/workspace/Workspace.css b/src/app/pages/workspace/Workspace.css index 1edc1d5..efcc3ff 100644 --- a/src/app/pages/workspace/Workspace.css +++ b/src/app/pages/workspace/Workspace.css @@ -17,4 +17,10 @@ margin: 0.1em; background-color: rgb(244, 244, 244); border-radius: 2%; +} + +.inner-panel { + overflow: auto; + max-height: 100%; + max-width: 100%; } \ No newline at end of file diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx index 7344b8b..d7c771e 100644 --- a/src/app/pages/workspace/Workspace.jsx +++ b/src/app/pages/workspace/Workspace.jsx @@ -31,23 +31,29 @@ export default function Workspace() { return ( - + - + {/* data table component */} - +

+ +
- + {/* visualization component */} - +
+ +
- + {/* modification component */} - +
+ +
) diff --git a/src/app/pages/workspace/visualization/Diagram.jsx b/src/app/pages/workspace/visualization/Diagram.jsx new file mode 100644 index 0000000..68b1121 --- /dev/null +++ b/src/app/pages/workspace/visualization/Diagram.jsx @@ -0,0 +1,26 @@ +import * as Plot from "@observablehq/plot"; +import Document from "../../../../utils/Document" +import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; +import { useStatesContext } from "../../../../providers/StatesProvider"; +import DIAGRAM_TYPE from "../../../../constants/DiagramType"; + + +/** + * React component of a d3.js diagram + */ +export default function Diagram() { + const { diagramState } = useStatesContext() + const { workspace } = useWorkspaceContext() + const diagramEntity = workspace.diagramEntity + + if (!diagramEntity) { return <> } + + // diagramEntity.setType(DIAGRAM_TYPE.HIST) + // diagramEntity.setOption("x", "age") + try { + const plotOptions = diagramEntity.generatePlotOptions() + return Plot.plot({ ...plotOptions, document: new Document() }).toHyperScript(); + } catch (error) { + return

{error.message}

+ } +} \ No newline at end of file diff --git a/src/app/pages/workspace/visualization/Visualization.jsx b/src/app/pages/workspace/visualization/Visualization.jsx index 9072fbf..c7c8aa2 100644 --- a/src/app/pages/workspace/visualization/Visualization.jsx +++ b/src/app/pages/workspace/visualization/Visualization.jsx @@ -1,4 +1,4 @@ -import Diagram from "../../../../components/Diagram"; +import Diagram from "./Diagram"; /** * A component containing diagrams from a workspace. diff --git a/src/entities/WorkspaceEntity.js b/src/entities/WorkspaceEntity.js index 97ad9f1..9e4108a 100644 --- a/src/entities/WorkspaceEntity.js +++ b/src/entities/WorkspaceEntity.js @@ -7,6 +7,9 @@ export default class WorkspaceEntity { setDataEntity(dataEntity) { this.dataEntity = dataEntity + if (this.diagramEntity) { + this.diagramEntity.setLinkedData(this.dataEntity) + } } setDiagramEntity(diagramEntity) { From 3c3f7b02dd579162608cde1629a34b04e714106f Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 3 Oct 2024 12:43:34 +0200 Subject: [PATCH 43/65] Use DataGrid for editable table --- .../pages/workspace/datatables}/DataTable.jsx | 2 +- .../workspace/datatables/DataTableTabs.jsx | 73 +++++++++++++++---- .../workspace/datatables/TableToolbar.jsx | 64 ++++++++++++++++ src/components/Diagram.jsx | 24 ------ 4 files changed, 125 insertions(+), 38 deletions(-) rename src/{components => app/pages/workspace/datatables}/DataTable.jsx (79%) create mode 100644 src/app/pages/workspace/datatables/TableToolbar.jsx delete mode 100644 src/components/Diagram.jsx diff --git a/src/components/DataTable.jsx b/src/app/pages/workspace/datatables/DataTable.jsx similarity index 79% rename from src/components/DataTable.jsx rename to src/app/pages/workspace/datatables/DataTable.jsx index 9e54c4b..369060c 100644 --- a/src/components/DataTable.jsx +++ b/src/app/pages/workspace/datatables/DataTable.jsx @@ -1,4 +1,4 @@ -import DataEntity from "../entities/DataEntity"; +import DataEntity from "../../../../entities/DataEntity"; /** * A table element that present a data entity diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index 8a26ce3..dee4dd8 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -1,9 +1,5 @@ - -/** - * A tabs component containing data tables from a workspace. - * - * @returns {ReactNode} Rendered tabs component. - */ +import { TrendingUp } from "@mui/icons-material"; +import { useStatesContext } from "../../../../providers/StatesProvider"; import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; import { Table, @@ -15,28 +11,79 @@ import { Typography, Paper, } from "@mui/material"; +import { DataGrid } from '@mui/x-data-grid'; +import TableToolbar from "./TableToolbar"; +import { useState } from "react"; + + + +function _prepareColumns(dataEntity) { + const prepareCol = (col) => { + return { + field: col, + headerName: col, + editable: col !== "id", + } + }; + const userInfo = dataEntity.userInfoColumns.map(prepareCol); + const questions = dataEntity.questionColumns.map(prepareCol); + const transform = dataEntity.transformColumns.map(prepareCol); + return userInfo.concat(questions, transform); +} +/** + * A tabs component containing data tables from a workspace. + * + * @returns {ReactNode} Rendered tabs component. + */ export default function DataTableTabs() { + // ---------- Danger zone starts here ---------- const { workspace } = useWorkspaceContext(); const dataEntity = workspace.dataEntity; - - console.log("Data Entity:", dataEntity); - - if (!dataEntity || !dataEntity.allColumns) { + const { + tableState, + updateTable, + updateAll + } = useStatesContext(); + const [currentCol, setCurrentCol] = useState("") + + // ---------- Danger zone ends here ---------- + + + if (!dataEntity) { return ( No data available ); } - + + const columns = _prepareColumns(dataEntity); + return (
😊 Deine Daten: - + setCurrentCol(param.field)} + processRowUpdate={(newRow, oldRow) => { + const rowNr = dataEntity.data.findIndex((row) => row.id === oldRow.id) + dataEntity.setValue(rowNr, currentCol, newRow[currentCol]) + console.log(dataEntity) + return newRow + }} + onProcessRowUpdateError={(error) => { + console.log(error) + }} + /> + + {/*
{dataEntity.allColumns.map((column) => ( @@ -65,7 +112,7 @@ export default function DataTableTabs() { )} -
+ */}
); diff --git a/src/app/pages/workspace/datatables/TableToolbar.jsx b/src/app/pages/workspace/datatables/TableToolbar.jsx new file mode 100644 index 0000000..56d441e --- /dev/null +++ b/src/app/pages/workspace/datatables/TableToolbar.jsx @@ -0,0 +1,64 @@ +import { useState } from 'react' +import { Box, Button, Popover, Typography } from "@mui/material"; +import { GridToolbarColumnsButton, GridToolbarContainer, GridToolbarExport } from "@mui/x-data-grid" +import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; +import { useStatesContext } from '../../../../providers/StatesProvider'; + + + +export default function TableToolbar() { + return ( + + + + + + + ); +} + + +function SetColumnsBtn() { + // ---------- Danger zone starts here ---------- + const { workspace } = useWorkspaceContext() + const { updateTable } = useStatesContext() + const [anchorEl, setAnchorEl] = useState(null) + const open = Boolean(anchorEl) + const id = open ? 'simple-popover' : undefined; + const handleClick = (event) => setAnchorEl(event.currentTarget) + const handleClose = () => setAnchorEl(null) + + // ---------- Danger zone ends here ---------- + + + + return ( +
+ + + Your code here + +
+ ) +} \ No newline at end of file diff --git a/src/components/Diagram.jsx b/src/components/Diagram.jsx deleted file mode 100644 index 9cb30c4..0000000 --- a/src/components/Diagram.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as Plot from "@observablehq/plot"; -import Document from "../utils/Document" -import { useWorkspaceContext } from "../providers/WorkspaceProvider"; -import { useStatesContext } from "../providers/StatesProvider"; - - -/** - * React component of a d3.js diagram - */ -export default function Diagram() { - const { diagramState } = useStatesContext() - const { workspace } = useWorkspaceContext() - const diagramEntity = workspace.diagramEntity - - if (!diagramEntity) { return <> } - - // diagramEntity.setOption("x", "age") - try { - const plotOptions = diagramEntity.generatePlotOptions() - return Plot.plot({ ...plotOptions, document: new Document() }).toHyperScript(); - } catch (error) { - return

{error.message}

- } -} \ No newline at end of file From 65c782cabb4c4c39996770533586ffc0b8746f4a Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 3 Oct 2024 14:52:03 +0200 Subject: [PATCH 44/65] Add report functionality --- .../workspace/datatables/DataTableTabs.jsx | 10 ++- .../workspace/datatables/TableToolbar.jsx | 70 ++++++++++++++++--- src/services/DataAnalysisService.js | 25 ++++++- src/utils/MathUtils.js | 9 +++ 4 files changed, 97 insertions(+), 17 deletions(-) diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index dee4dd8..78df8b9 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -2,12 +2,7 @@ import { TrendingUp } from "@mui/icons-material"; import { useStatesContext } from "../../../../providers/StatesProvider"; import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; import { - Table, - TableBody, - TableCell, TableContainer, - TableHead, - TableRow, Typography, Paper, } from "@mui/material"; @@ -73,8 +68,11 @@ export default function DataTableTabs() { disableRowSelectionOnClick={true} onCellEditStart={(param) => setCurrentCol(param.field)} processRowUpdate={(newRow, oldRow) => { + let value = newRow[currentCol].trim() + if (/^\d+$/.test(value)) { value = parseFloat(value) } // Check if input is a number + const rowNr = dataEntity.data.findIndex((row) => row.id === oldRow.id) - dataEntity.setValue(rowNr, currentCol, newRow[currentCol]) + dataEntity.setValue(rowNr, currentCol, value) console.log(dataEntity) return newRow }} diff --git a/src/app/pages/workspace/datatables/TableToolbar.jsx b/src/app/pages/workspace/datatables/TableToolbar.jsx index 56d441e..722eccf 100644 --- a/src/app/pages/workspace/datatables/TableToolbar.jsx +++ b/src/app/pages/workspace/datatables/TableToolbar.jsx @@ -1,8 +1,19 @@ import { useState } from 'react' -import { Box, Button, Popover, Typography } from "@mui/material"; +import { Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + Popover, + Typography +} from "@mui/material"; import { GridToolbarColumnsButton, GridToolbarContainer, GridToolbarExport } from "@mui/x-data-grid" import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; import { useStatesContext } from '../../../../providers/StatesProvider'; +import SummarizeIcon from '@mui/icons-material/Summarize'; +import TableChartIcon from '@mui/icons-material/TableChart'; +import dataAnalysisService from '../../../../services/DataAnalysisService'; @@ -13,25 +24,62 @@ export default function TableToolbar() { + ); } +function MyReportBtn() { +// ---------- Danger zone starts here ---------- + const { workspace } = useWorkspaceContext() + const dataEntity = workspace.dataEntity + const [open, setOpen] = useState(false) + const handleClickOpen = () => setOpen(true) + const handleClose = () => setOpen(false) + + const report = dataAnalysisService.getReport(dataEntity) +// ---------- Danger zone ends here ---------- + + + return ( +
+ + + + + {JSON.stringify(report)} + + + + + + +
+ ) +} + + +// ---------- Danger zone starts here ---------- function SetColumnsBtn() { - // ---------- Danger zone starts here ---------- const { workspace } = useWorkspaceContext() const { updateTable } = useStatesContext() const [anchorEl, setAnchorEl] = useState(null) const open = Boolean(anchorEl) - const id = open ? 'simple-popover' : undefined; + const id = open ? 'simple-popover' : undefined const handleClick = (event) => setAnchorEl(event.currentTarget) const handleClose = () => setAnchorEl(null) - // ---------- Danger zone ends here ---------- - - - + + + return (
+ > Your code here
) -} \ No newline at end of file +} +// ---------- Danger zone ends here ---------- \ No newline at end of file diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 13b3f31..1f838a2 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -1,5 +1,5 @@ import DataEntity from "../entities/DataEntity" -import { average } from "../utils/MathUtils" +import { average, count } from "../utils/MathUtils" class DataAnalysisService { @@ -22,6 +22,29 @@ class DataAnalysisService { return average(this.calculateScores(dataEntity)) } + + getReport(dataEntity) { + const result = {} + result.userInfo = {} + result.transform = {} + if (dataEntity.size === 0 || !dataEntity) { return result } + + dataEntity.userInfoColumns.forEach(col => { + if (col === "id") { return } + const valueArr = dataEntity.col(col) + if (typeof dataEntity.loc(0, col) === "number") { + result.userInfo[col] = { + avg: average(valueArr) + } + } + else { + result.userInfo[col] = count(valueArr) + } + }) + + return result + } + } diff --git a/src/utils/MathUtils.js b/src/utils/MathUtils.js index 9003f52..c00a674 100644 --- a/src/utils/MathUtils.js +++ b/src/utils/MathUtils.js @@ -8,6 +8,14 @@ function average(arr) { } +function count(arr) { + return arr.reduce((count, item) => { + count[item] = (count[item] || 0) + 1 + return count; + }, {}) +} + + /** * @param {number[]} arr1 * @param {number[] | number} arr2 @@ -49,6 +57,7 @@ function apply(arr, func) { export { average, + count, inplaceOperation, apply } \ No newline at end of file From 1600e75cfae5c09518adbc43481224b32e668ec5 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 3 Oct 2024 15:38:58 +0200 Subject: [PATCH 45/65] Minor fix --- src/services/DataAnalysisService.js | 33 ++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 1f838a2..4111bfc 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -1,3 +1,4 @@ +import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" import DataEntity from "../entities/DataEntity" import { average, count } from "../utils/MathUtils" @@ -25,22 +26,44 @@ class DataAnalysisService { getReport(dataEntity) { const result = {} - result.userInfo = {} result.transform = {} + result.userInfo = {} + result.questions = {} + + const getNumericReport = (valueArr) => { + return { + avg: average(valueArr) + } + } + const getCategoricalReport = (valueArr) => { + return { + count: count(valueArr) + } + } + + if (dataEntity.size === 0 || !dataEntity) { return result } dataEntity.userInfoColumns.forEach(col => { if (col === "id") { return } const valueArr = dataEntity.col(col) if (typeof dataEntity.loc(0, col) === "number") { - result.userInfo[col] = { - avg: average(valueArr) - } + result.userInfo[col] = getNumericReport(valueArr) } else { - result.userInfo[col] = count(valueArr) + result.userInfo[col] = getCategoricalReport(valueArr) } }) + dataEntity.questionColumns.forEach(col => { + result.questions[col] = getNumericReport(dataEntity.col(col)) + }) + dataEntity.transformColumns.forEach(col => { + result.transform[col] = getNumericReport(dataEntity.col(col)) + }) + + if (dataEntity.type !== QUESTIONNAIRE_TYPE.NONE) { + result.score = this.calculateAverageScore(dataEntity) + } else {result.score = "Undefined"} return result } From e0148bbd306e0d916141269de59d0e34d0e684b4 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Thu, 3 Oct 2024 18:55:49 +0200 Subject: [PATCH 46/65] Add simple modification ui --- src/app/pages/workspace/Workspace.css | 2 +- .../workspace/modification/Modification.jsx | 62 ++++++++++++++++++- src/entities/DiagramEntity.js | 2 +- src/exceptions/GlobalErrorHandler.js | 0 src/services/DiagramService.js | 0 src/services/WorkspaceService.js | 0 6 files changed, 62 insertions(+), 4 deletions(-) delete mode 100644 src/exceptions/GlobalErrorHandler.js delete mode 100644 src/services/DiagramService.js delete mode 100644 src/services/WorkspaceService.js diff --git a/src/app/pages/workspace/Workspace.css b/src/app/pages/workspace/Workspace.css index efcc3ff..7a5d2c0 100644 --- a/src/app/pages/workspace/Workspace.css +++ b/src/app/pages/workspace/Workspace.css @@ -15,7 +15,7 @@ .panel { margin: 0.1em; - background-color: rgb(244, 244, 244); + background-color: rgb(250, 250, 247); border-radius: 2%; } diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index e0c05ba..a13411d 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -1,22 +1,80 @@ +import { useState } from "react" import { useStatesContext } from "../../../../providers/StatesProvider" import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider" +import { Box, Button, DialogActions, Divider, FormControl, MenuItem, Select, TextField } from "@mui/material" +import DIAGRAM_TYPE from "../../../../constants/DiagramType" + + +const availablDiagTypes = Object.values(DIAGRAM_TYPE) +availablDiagTypes.shift() + /** * The user interface for modifying diagram options. */ function Modification() { - const { modificationState, updateDiagram } = useStatesContext() + const { modificationState, updateDiagram, updateModification } = useStatesContext() const { workspace } = useWorkspaceContext() + const [options, setOptions] = useState({}) // The diagram entity to be worked on const diagramEntity = workspace.diagramEntity if (!diagramEntity) { return <> } + const handleInputChange = (event) => { + const { name, value } = event.target + options[name] = value + } + + const handleTypeChange = (event) => { + diagramEntity.setType(event.target.value) + updateModification() + updateDiagram() + } + + const confirm = (event) => { + Object.keys(options).map(opt => diagramEntity.setOption(opt, options[opt])) + updateDiagram() + } + const allOptions = diagramEntity.allOptions const requiredOptions = diagramEntity.requiredOptions return ( -

Modification

+
+ + + + Options + + {allOptions.map((opt, index) => ( + + ))} + + + + + + +
) } diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js index 305dd10..e2c253a 100644 --- a/src/entities/DiagramEntity.js +++ b/src/entities/DiagramEntity.js @@ -109,7 +109,7 @@ export default class DiagramEntity { } - get AllOptions() { return this.type.options } + get allOptions() { return this.type.options } get requiredOptions() { return this.type.requiredOptions } diff --git a/src/exceptions/GlobalErrorHandler.js b/src/exceptions/GlobalErrorHandler.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/services/DiagramService.js b/src/services/DiagramService.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/services/WorkspaceService.js b/src/services/WorkspaceService.js deleted file mode 100644 index e69de29..0000000 From ce2f34e9cff6cc9b581b86767878657aec34ae56 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 4 Oct 2024 20:17:19 +0200 Subject: [PATCH 47/65] Fix bug in NPS calculator --- src/constants/QuestionnaireType.js | 7 +++++-- src/services/DataAnalysisService.js | 10 +++++++--- tests/services/DataAnalysisService.test.js | 9 ++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/constants/QuestionnaireType.js b/src/constants/QuestionnaireType.js index 537b265..4ca3898 100644 --- a/src/constants/QuestionnaireType.js +++ b/src/constants/QuestionnaireType.js @@ -152,7 +152,10 @@ QUESTIONNAIRE_TYPE.NPS = { minValue: 0, maxValue: 10, - scoreCalculator: (npsresults) => { + // NPS does not define score for individual + scoreCalculator: x => x, + + totalScoreCalculator: (npsresults) => { let promoters = 0; let detractors = 0; let neutrals = 0; @@ -160,7 +163,7 @@ QUESTIONNAIRE_TYPE.NPS = { // nps-Score for (let i = 0; i < npsresults.length; i++) { - let score = npsresults[i] + let score = npsresults[i].Q1 if (score >= 9){ promoters++; diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 4111bfc..6a41f3b 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -16,10 +16,14 @@ class DataAnalysisService { /** - * Calculate average score of a DataEntity according to its questionnaire type. + * Calculate total score of a DataEntity according to its questionnaire type. + * For most questionnaires, this means the average score. * @param {DataEntity} dataEntity */ - calculateAverageScore(dataEntity) { + calculateTotalScore(dataEntity) { + if (dataEntity.type === QUESTIONNAIRE_TYPE.NPS) { + return QUESTIONNAIRE_TYPE.NPS.totalScoreCalculator(dataEntity.data) + } return average(this.calculateScores(dataEntity)) } @@ -62,7 +66,7 @@ class DataAnalysisService { }) if (dataEntity.type !== QUESTIONNAIRE_TYPE.NONE) { - result.score = this.calculateAverageScore(dataEntity) + result.score = this.calculateTotalScore(dataEntity) } else {result.score = "Undefined"} return result diff --git a/tests/services/DataAnalysisService.test.js b/tests/services/DataAnalysisService.test.js index 1ec45ba..71fd791 100644 --- a/tests/services/DataAnalysisService.test.js +++ b/tests/services/DataAnalysisService.test.js @@ -12,7 +12,7 @@ test( QUESTIONNAIRE_TYPE.NONE, [[5,5,5,5,5,5,5,5,5,5,5,5,5]] ) - expect(() => dataAnalysisService.calculateAverageScore(testDataEntity)) + expect(() => dataAnalysisService.calculateTotalScore(testDataEntity)) .toThrow(QuestionnaireTypeError) } ) @@ -28,7 +28,7 @@ test( [5,5,5,5,5,5,5,5,5,5] ] ) - const susScore = dataAnalysisService.calculateAverageScore(testDataEntity) + const susScore = dataAnalysisService.calculateTotalScore(testDataEntity) assert(susScore === 50, "Test SUS data should have SUS score 50.") } ) @@ -38,10 +38,9 @@ test( () => { const testDataEntity = generateDataEntity( QUESTIONNAIRE_TYPE.NPS, - [[1,8,8,7,9,9,9,9] - ] + [[1],[8],[8],[7],[9],[9],[9],[9]] ) - const npsScore = dataAnalysisService.calculateAverageScore(testDataEntity) + const npsScore = dataAnalysisService.calculateTotalScore(testDataEntity) assert(Math.abs(npsScore - 37.5) < 0.1, "Test NPS data should have NPS score 37.5.") } ) From 344ca9f7d9092eaa56c116fd29b759dd5524da8d Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Fri, 4 Oct 2024 20:17:31 +0200 Subject: [PATCH 48/65] Fix bug in modification --- src/app/pages/workspace/Workspace.css | 10 ++++- .../workspace/modification/Modification.jsx | 41 +++++++++++-------- .../workspace/modification/OptionFields.jsx | 26 ++++++++++++ 3 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 src/app/pages/workspace/modification/OptionFields.jsx diff --git a/src/app/pages/workspace/Workspace.css b/src/app/pages/workspace/Workspace.css index 7a5d2c0..4a97377 100644 --- a/src/app/pages/workspace/Workspace.css +++ b/src/app/pages/workspace/Workspace.css @@ -21,6 +21,12 @@ .inner-panel { overflow: auto; - max-height: 100%; - max-width: 100%; + height: 100%; + width: 100%; +} + +svg { + height: 100%; + width: 100%; + vertical-align: center; } \ No newline at end of file diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index a13411d..dd3ab58 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -1,13 +1,20 @@ -import { useState } from "react" +import { useEffect, useState } from "react" import { useStatesContext } from "../../../../providers/StatesProvider" import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider" import { Box, Button, DialogActions, Divider, FormControl, MenuItem, Select, TextField } from "@mui/material" import DIAGRAM_TYPE from "../../../../constants/DiagramType" +import OptionFields from "./OptionFields" const availablDiagTypes = Object.values(DIAGRAM_TYPE) availablDiagTypes.shift() +const emptyOptions = (allOptions) => { + const options = {} + allOptions.forEach(opt => options[opt] = "") + return options +} + /** * The user interface for modifying diagram options. @@ -15,20 +22,25 @@ availablDiagTypes.shift() function Modification() { const { modificationState, updateDiagram, updateModification } = useStatesContext() const { workspace } = useWorkspaceContext() - const [options, setOptions] = useState({}) - - // The diagram entity to be worked on const diagramEntity = workspace.diagramEntity if (!diagramEntity) { return <> } + + const allOptions = diagramEntity.allOptions + const requiredOptions = diagramEntity.requiredOptions + const [options, setOptions] = useState(emptyOptions(allOptions)) + const handleInputChange = (event) => { const { name, value } = event.target - options[name] = value + setOptions({ + ...options, + [name]: value + }) } const handleTypeChange = (event) => { diagramEntity.setType(event.target.value) - updateModification() + setOptions(emptyOptions(diagramEntity.allOptions)) updateDiagram() } @@ -37,8 +49,6 @@ function Modification() { updateDiagram() } - const allOptions = diagramEntity.allOptions - const requiredOptions = diagramEntity.requiredOptions return (
@@ -57,15 +67,12 @@ function Modification() { Options - {allOptions.map((opt, index) => ( - - ))} + diff --git a/src/app/pages/workspace/modification/OptionFields.jsx b/src/app/pages/workspace/modification/OptionFields.jsx new file mode 100644 index 0000000..b87ef3f --- /dev/null +++ b/src/app/pages/workspace/modification/OptionFields.jsx @@ -0,0 +1,26 @@ +import { TextField } from "@mui/material"; +import { useEffect } from "react"; + + +export default function OptionFields({ + options, + allOptions, + requiredOptions, + onChange +}) { + + return ( + <> + {allOptions.map((opt, index) => ( + + ))} + + ) +} \ No newline at end of file From 63b3f763c130af3b6a136c3b8850ab871f287823 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sat, 5 Oct 2024 12:22:06 +0200 Subject: [PATCH 49/65] Separate diagram options and settings --- src/app/pages/workspace/Workspace.jsx | 4 +- .../modification/DiagramSettings.jsx | 28 +++++++++++++ .../workspace/modification/Modification.jsx | 9 +++- .../workspace/modification/OptionFields.jsx | 8 +++- src/constants/DiagramSetting.js | 40 ++++++++++++++++++ src/constants/DiagramType.js | 41 +++++-------------- src/entities/DiagramEntity.js | 29 +++++++++---- 7 files changed, 117 insertions(+), 42 deletions(-) create mode 100644 src/app/pages/workspace/modification/DiagramSettings.jsx create mode 100644 src/constants/DiagramSetting.js diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx index d7c771e..16f900c 100644 --- a/src/app/pages/workspace/Workspace.jsx +++ b/src/app/pages/workspace/Workspace.jsx @@ -31,7 +31,7 @@ export default function Workspace() { return ( - + {/* data table component */} @@ -49,7 +49,7 @@ export default function Workspace() { - + {/* modification component */}
diff --git a/src/app/pages/workspace/modification/DiagramSettings.jsx b/src/app/pages/workspace/modification/DiagramSettings.jsx new file mode 100644 index 0000000..be2c014 --- /dev/null +++ b/src/app/pages/workspace/modification/DiagramSettings.jsx @@ -0,0 +1,28 @@ +import { Checkbox, FormControl, FormControlLabel, FormGroup } from "@mui/material"; +import { DIAGRAM_SETTING } from "../../../../constants/DiagramSetting"; + + + +const boolSettingList = Object.values(DIAGRAM_SETTING.BOOL) + + +export default function DiagramSettings( + +) { + return ( +
+ + + {boolSettingList.map(setting => ( + + } + label={setting.name} + /> + ))} + + +
+ ) +} \ No newline at end of file diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index dd3ab58..9d31a27 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -4,6 +4,7 @@ import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider" import { Box, Button, DialogActions, Divider, FormControl, MenuItem, Select, TextField } from "@mui/material" import DIAGRAM_TYPE from "../../../../constants/DiagramType" import OptionFields from "./OptionFields" +import DiagramSettings from "./DiagramSettings" const availablDiagTypes = Object.values(DIAGRAM_TYPE) @@ -53,6 +54,8 @@ function Modification() { return (
+ Diagram Type + @@ -103,11 +123,16 @@ function Home() { +
+ {/* Datei-Upload-Button*/} +

- + + +
@@ -122,19 +147,19 @@ function Home() {

- + + +
{/* use Link for redirecting */}
- - - +

Impressum!!!!!!!!!!!!!!!!!!!!! From e8c355bb6af8b557380e747083ec02438284fe9e Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sat, 5 Oct 2024 18:38:16 +0200 Subject: [PATCH 54/65] Adjust appearance of data table --- .../workspace/datatables/DataTableTabs.jsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index 84db184..aee78b1 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -13,16 +13,17 @@ import { useState } from "react"; function _prepareColumns(dataEntity) { - const prepareCol = (col) => { + const prepareCol = (type) => (col) => { return { field: col, headerName: col, editable: col !== "id", + headerClassName: `${type}-header` } }; - const userInfo = dataEntity.userInfoColumns.map(prepareCol); - const questions = dataEntity.questionColumns.map(prepareCol); - const transform = dataEntity.transformColumns.map(prepareCol); + const userInfo = dataEntity.userInfoColumns.map(prepareCol("userInfo")); + const questions = dataEntity.questionColumns.map(prepareCol("questions")); + const transform = dataEntity.transformColumns.map(prepareCol("transform")); return userInfo.concat(transform, questions); } @@ -60,10 +61,27 @@ export default function DataTableTabs() { 😊 Deine Daten: - + setCurrentCol(param.field)} From e779307a22546b98e2e0b020e466e2d9765758fd Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sun, 6 Oct 2024 09:38:41 +0200 Subject: [PATCH 55/65] Parse transform column input --- .../workspace/modification/Modification.jsx | 9 +++++++- src/entities/DataEntity.js | 22 +++++++++---------- src/utils/DataUtils.js | 18 +++++++++++++-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index 9d31a27..cd8ee29 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -5,6 +5,7 @@ import { Box, Button, DialogActions, Divider, FormControl, MenuItem, Select, Tex import DIAGRAM_TYPE from "../../../../constants/DiagramType" import OptionFields from "./OptionFields" import DiagramSettings from "./DiagramSettings" +import { parseColumnInput } from "../../../../utils/DataUtils" const availablDiagTypes = Object.values(DIAGRAM_TYPE) @@ -24,6 +25,7 @@ function Modification() { const { modificationState, updateDiagram, updateModification } = useStatesContext() const { workspace } = useWorkspaceContext() const diagramEntity = workspace.diagramEntity + const dataEntity = workspace.dataEntity if (!diagramEntity) { return <> } const allOptions = diagramEntity.allOptions @@ -46,7 +48,12 @@ function Modification() { } const confirm = (event) => { - Object.keys(options).map(opt => diagramEntity.setOption(opt, options[opt])) + Object.keys(options).map(opt => { + let value = null + if (opt === "x" || opt === "y") { value = parseColumnInput(options[opt], dataEntity) } + else { value = options[opt]} + diagramEntity.setOption(opt, value) + }) updateDiagram() } diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 77d3e14..5c8f2e5 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -19,6 +19,7 @@ export default class DataEntity { this.type = type this.data = data this.columns = { userInfo: [], questions: [], transform: [] } + this.transformId = 1 if (data.length === 0) { return } this.columns.questions = Object.keys(data[0]).filter(k => columnType(k) === "questions") @@ -99,20 +100,19 @@ export default class DataEntity { } /** - * Given a string or an array of strings, create transform columns with those name and + * Given a string, create transform columns with those name and * fill those columns with the transformed data. - * @param {string | string[]} expressions + * @param {string} expressions */ - addTransformColumns(expressions) { - if (typeof expressions === "string") { expressions = [expressions] } - let columns = expressions.map(col => `T:${col}`) - columns = columns.filter(col => !this.transformColumns.includes(col)) + addTransformColumns(expression) { + let column = `T${this.transformId}:${expression}` + if (this.transformColumns.includes(column)) { return } - expressions.forEach((expr, colIndex) => { - const results = evaluate(expr, this) - this.data.forEach((row, rowNr) => row[columns[colIndex]] = results[rowNr]) - }) - this.columns.transform = this.columns.transform.concat(columns) + const results = evaluate(expression, this) + this.data.forEach((row, rowNr) => row[column] = results[rowNr]) + + this.columns.transform.push(column) + this.transformId++ } /** diff --git a/src/utils/DataUtils.js b/src/utils/DataUtils.js index 59d256d..f34b286 100644 --- a/src/utils/DataUtils.js +++ b/src/utils/DataUtils.js @@ -4,7 +4,7 @@ import QUESTIONNAIRE_TYPE from "../constants/QuestionnaireType" function columnType(columnName) { if (columnName.match(/^Q[1-9][0-9]*/g)) { return "questions" } - if (columnName.match(/^T:*/g)) { return "transform" } + if (columnName.match(/^T[1-9][0-9]*:/g)) { return "transform" } return "userInfo" } @@ -70,9 +70,23 @@ function generateEmptyRow(columns) { } +function parseColumnInput(input, dataEntity) { + input = input.trim() + if (!input.match(/^T[1-9][0-9]*$/g)) { return input } + + for (let col of dataEntity.transformColumns) { + if (col.startsWith(input)) { + console.log(col) + return col + } + } +} + + export { columnType, infoResultSplit, generateResultColumns, - generateEmptyRow + generateEmptyRow, + parseColumnInput } \ No newline at end of file From 112b23084c517f97777573a332be302ba43a6ce5 Mon Sep 17 00:00:00 2001 From: Atocas <87466604+Atocas@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:01:33 +0200 Subject: [PATCH 56/65] TableToolbar Report Update --- .../workspace/datatables/TableToolbar.jsx | 132 ++++++++++-------- 1 file changed, 77 insertions(+), 55 deletions(-) diff --git a/src/app/pages/workspace/datatables/TableToolbar.jsx b/src/app/pages/workspace/datatables/TableToolbar.jsx index 30658cb..6324bc5 100644 --- a/src/app/pages/workspace/datatables/TableToolbar.jsx +++ b/src/app/pages/workspace/datatables/TableToolbar.jsx @@ -1,14 +1,18 @@ import { useState } from 'react' -import { Box, +import { + Box, Button, Dialog, DialogActions, DialogContent, DialogContentText, + Typography, + List, + ListItem, + ListItemText, Popover, Stack, TextField, - Typography } from "@mui/material"; import { GridToolbarColumnsButton, GridToolbarContainer, GridToolbarExport } from "@mui/x-data-grid" import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; @@ -17,8 +21,6 @@ import SummarizeIcon from '@mui/icons-material/Summarize'; import TableChartIcon from '@mui/icons-material/TableChart'; import dataAnalysisService from '../../../../services/DataAnalysisService'; - - export default function TableToolbar() { return ( @@ -31,19 +33,43 @@ export default function TableToolbar() { ); } +// Function to capitalize the categories inside the Report +function capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +// Function to make the Report more human readable +function formatReport(report) { + if (typeof report === 'object' && report !== null) { + return ( + + {Object.entries(report).map(([key, value]) => ( + + + + ))} + + ); + } + + // Return a string without brackets if it's not an Object + return {JSON.stringify(report).replace(/[{}[\]]/g, '')}; +} function MyReportBtn() { -// ---------- Danger zone starts here ---------- - const { workspace } = useWorkspaceContext() - const dataEntity = workspace.dataEntity - const [open, setOpen] = useState(false) - const handleClickOpen = () => setOpen(true) - const handleClose = () => setOpen(false) + // ---------- Danger zone starts here ---------- + const { workspace } = useWorkspaceContext(); + const dataEntity = workspace.dataEntity; + const [open, setOpen] = useState(false); + const handleClickOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + const report = dataAnalysisService.getReport(dataEntity); + // ---------- Danger zone ends here ---------- - const report = dataAnalysisService.getReport(dataEntity) -// ---------- Danger zone ends here ---------- - - return (

- ) + ); } - // ---------- Danger zone starts here ---------- function SetColumnsBtn() { - const { workspace } = useWorkspaceContext() - const dataEntity = workspace.dataEntity - const { updateTable } = useStatesContext() - const [anchorEl, setAnchorEl] = useState(null) - const open = Boolean(anchorEl) - const id = open ? 'simple-popover' : undefined - const handleClick = (event) => setAnchorEl(event.currentTarget) - const handleClose = () => setAnchorEl(null) + const { workspace } = useWorkspaceContext(); + const dataEntity = workspace.dataEntity; + const { updateTable } = useStatesContext(); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const id = open ? 'simple-popover' : undefined; + const handleClick = (event) => setAnchorEl(event.currentTarget); + const handleClose = () => setAnchorEl(null); - const [questionNr, setQuestionNr] = useState("") - const [newUserInfo, setNewUserInfo] = useState("") - const [newTransform, setNewTransform] = useState("") - const [deleteColumn, setDeleteColumn] = useState("") + const [questionNr, setQuestionNr] = useState(""); + const [newUserInfo, setNewUserInfo] = useState(""); + const [newTransform, setNewTransform] = useState(""); + const [deleteColumn, setDeleteColumn] = useState(""); const applyChanges = () => { if (/^\d+$/.test(questionNr)) { - dataEntity.setNumOfQuestions(parseInt(questionNr)) + dataEntity.setNumOfQuestions(parseInt(questionNr)); } if (newUserInfo) { - dataEntity.addUserInfoColumns(newUserInfo) + dataEntity.addUserInfoColumns(newUserInfo); } if (newTransform) { - dataEntity.addTransformColumns(newTransform) + dataEntity.addTransformColumns(newTransform); } if (deleteColumn) { - dataEntity.deleteColumns(deleteColumn) + dataEntity.deleteColumns(deleteColumn); } - updateTable() - } - - + updateTable(); + }; + return (
- + > + - setQuestionNr(event.target.value.trim())} /> - setNewUserInfo(event.target.value.trim())} /> - setNewTransform(event.target.value.trim())} /> - setDeleteColumn(event.target.value.trim())} /> - + - - +
- ) + ); } // ---------- Danger zone ends here ---------- \ No newline at end of file From 714459323091f71ff2b45f5df011e9bad0cfaeef Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Sun, 6 Oct 2024 16:46:18 +0200 Subject: [PATCH 57/65] Landing page small opticla Details --- src/app/pages/home/Home.css | 1 + src/app/pages/home/Home.jsx | 104 ++++++++++++++++++++++++------------ 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/app/pages/home/Home.css b/src/app/pages/home/Home.css index fa70b8a..d251482 100644 --- a/src/app/pages/home/Home.css +++ b/src/app/pages/home/Home.css @@ -40,6 +40,7 @@ border-radius: 8px; padding: 20px; margin: 5px 0; + text-align: left; } .fieldDataFile:hover { diff --git a/src/app/pages/home/Home.jsx b/src/app/pages/home/Home.jsx index 2e9d9af..a55ab1d 100644 --- a/src/app/pages/home/Home.jsx +++ b/src/app/pages/home/Home.jsx @@ -77,36 +77,9 @@ function Home() {

{/* Modal for Info UserData*/} -
-

Wir stellen dir hier ein Tool zur Verfügung, um deine Usability-Daten aus Fragebögen auszuwerten. Diese Anwendung ist für User Experience Questionnaire (UEQ), System Usability Scale (SUS), Net Promoter Score (NPS) und RAW Task Load Index geeignet. Für den erforderlichen Aufbau der Datei klicke hier.

- - -

Datenformat

-

- Zugelassen sind nur csv-Dateien. Diese müssen für die Analyse im folgenden Format vorliegen. - - - - - - - - - - - - - - - - - - -
125M
230W
322M
-

- -
-
+
+

Wir stellen dir hier ein Tool zur Verfügung, um deine Usability-Daten aus Fragebögen auszuwerten. Diese Anwendung ist für User Experience Questionnaire (UEQ), System Usability Scale (SUS), Net Promoter Score (NPS) und RAW Task Load Index geeignet. +

{/**/} @@ -115,7 +88,8 @@ function Home() {
{/* Upload Usability Data*/}
-

Hier kannst du deine Daten hochladen. Wähle zuerst den Usability-Fragebogen aus, welchen du gerne analysieren möchtest. Anschließend kannst du deine dazugehörige csv-Datei hochladen. Für Infos bzgl. des Aufbaus der Datei, öffne gerne die Infos oben.

+

Hier kannst du deine Daten hochladen.

+

1. Wähle deinen Usability-Fragebogen.


+

2. Lade deine csv-Datei hoch. Für Informationen zum Datei-Format klicke + hier.

{/* Datei-Upload-Button*/}
@@ -137,7 +113,8 @@ function Home() {
{/* Upload Usability Data*/} -

Hier kannst du deine Daten maskieren. Wähle zuerst den Usabiliyt-Fragebogen aus, welchen du gerne analysieren möchtest. Für Infos zum Datenformat klicke, HIER!

+

Hier kannst du über eine Maske Daten eingeben.

+

1. Wähle deinen Usability-Fragebogen.


+

2. Befülle die Maske. Für Informationen zum Datei-Format klicke hier.

+

+ + {/*

); diff --git a/src/app/pages/workspace/datatables/Toolbar/MyReportBtn.jsx b/src/app/pages/workspace/datatables/Toolbar/MyReportBtn.jsx new file mode 100644 index 0000000..25a122e --- /dev/null +++ b/src/app/pages/workspace/datatables/Toolbar/MyReportBtn.jsx @@ -0,0 +1,79 @@ +import { useState } from 'react' +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + Typography, + List, + ListItem, + ListItemText, +} from "@mui/material"; +import { useWorkspaceContext } from "../../../../../providers/WorkspaceProvider"; +import SummarizeIcon from '@mui/icons-material/Summarize'; +import dataAnalysisService from '../../../../../services/DataAnalysisService'; + + + +// Function to capitalize the categories inside the Report +function capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +// Function to make the Report more human readable +function formatReport(report) { + if (typeof report === 'object' && report !== null) { + return ( + + {Object.entries(report).map(([key, value]) => ( + + + + ))} + + ); + } + + // Return a string without brackets if it's not an Object + return {JSON.stringify(report).replace(/[{}[\]]/g, '')}; +} + +export default function MyReportBtn() { + // ---------- Danger zone starts here ---------- + const { workspace } = useWorkspaceContext(); + const dataEntity = workspace.dataEntity; + const [open, setOpen] = useState(false); + const handleClickOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + const report = dataAnalysisService.getReport(dataEntity); + // ---------- Danger zone ends here ---------- + + return ( +
+ + + + + Report Details + + {formatReport(report)} + + + + + +
+ ); +} \ No newline at end of file diff --git a/src/app/pages/workspace/datatables/TableToolbar.jsx b/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx similarity index 50% rename from src/app/pages/workspace/datatables/TableToolbar.jsx rename to src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx index 6324bc5..49d6851 100644 --- a/src/app/pages/workspace/datatables/TableToolbar.jsx +++ b/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx @@ -2,101 +2,18 @@ import { useState } from 'react' import { Box, Button, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - Typography, - List, - ListItem, - ListItemText, Popover, Stack, TextField, } from "@mui/material"; -import { GridToolbarColumnsButton, GridToolbarContainer, GridToolbarExport } from "@mui/x-data-grid" -import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; -import { useStatesContext } from '../../../../providers/StatesProvider'; -import SummarizeIcon from '@mui/icons-material/Summarize'; +import { useWorkspaceContext } from "../../../../../providers/WorkspaceProvider"; +import { useStatesContext } from '../../../../../providers/StatesProvider'; import TableChartIcon from '@mui/icons-material/TableChart'; -import dataAnalysisService from '../../../../services/DataAnalysisService'; -export default function TableToolbar() { - return ( - - - - - - - - ); -} - -// Function to capitalize the categories inside the Report -function capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -// Function to make the Report more human readable -function formatReport(report) { - if (typeof report === 'object' && report !== null) { - return ( - - {Object.entries(report).map(([key, value]) => ( - - - - ))} - - ); - } - - // Return a string without brackets if it's not an Object - return {JSON.stringify(report).replace(/[{}[\]]/g, '')}; -} - -function MyReportBtn() { - // ---------- Danger zone starts here ---------- - const { workspace } = useWorkspaceContext(); - const dataEntity = workspace.dataEntity; - const [open, setOpen] = useState(false); - const handleClickOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - const report = dataAnalysisService.getReport(dataEntity); - // ---------- Danger zone ends here ---------- - return ( -
- - - - - Report Details - - {formatReport(report)} - - - - - -
- ); -} -// ---------- Danger zone starts here ---------- -function SetColumnsBtn() { +export default function SetColumnsBtn() { const { workspace } = useWorkspaceContext(); const dataEntity = workspace.dataEntity; const { updateTable } = useStatesContext(); @@ -183,5 +100,4 @@ function SetColumnsBtn() {
); -} -// ---------- Danger zone ends here ---------- \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/pages/workspace/datatables/Toolbar/TableToolbar.jsx b/src/app/pages/workspace/datatables/Toolbar/TableToolbar.jsx new file mode 100644 index 0000000..dec90bb --- /dev/null +++ b/src/app/pages/workspace/datatables/Toolbar/TableToolbar.jsx @@ -0,0 +1,18 @@ +import { + Box, +} from "@mui/material"; +import { GridToolbarColumnsButton, GridToolbarContainer, GridToolbarExport } from "@mui/x-data-grid" +import SetColumnsBtn from './SetColumnsBtn'; +import MyReportBtn from './MyReportBtn'; + +export default function TableToolbar() { + return ( + + + + + + + + ); +} diff --git a/src/app/pages/workspace/modification/DiagramSettings.jsx b/src/app/pages/workspace/modification/DiagramSettings.jsx index 326a52b..a6fb150 100644 --- a/src/app/pages/workspace/modification/DiagramSettings.jsx +++ b/src/app/pages/workspace/modification/DiagramSettings.jsx @@ -2,7 +2,6 @@ import { Checkbox, FormControl, FormControlLabel, FormGroup } from "@mui/materia import { DIAGRAM_SETTING } from "../../../../constants/DiagramSetting"; import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider"; import { useStatesContext } from "../../../../providers/StatesProvider"; -import { useEffect } from "react"; @@ -10,7 +9,7 @@ const boolSettingList = Object.values(DIAGRAM_SETTING.BOOL) export default function DiagramSettings() { - const { updateDiagram, updateModification } = useStatesContext() + const { updateDiagram } = useStatesContext() const { workspace } = useWorkspaceContext() const diagramEntity = workspace.diagramEntity @@ -26,6 +25,7 @@ export default function DiagramSettings() { {boolSettingList.map(setting => ( { return { - avg: average(valueArr) + average: average(valueArr) } } const getCategoricalReport = (valueArr) => { @@ -46,7 +50,9 @@ class DataAnalysisService { } - if (dataEntity.size === 0 || !dataEntity) { return result } + if (!dataEntity || dataEntity.size === 0) { return result } + + result.dataSize = dataEntity.size dataEntity.userInfoColumns.forEach(col => { if (col === "id") { return } @@ -66,8 +72,12 @@ class DataAnalysisService { }) if (dataEntity.type !== QUESTIONNAIRE_TYPE.NONE) { - result.score = this.calculateTotalScore(dataEntity) - } else {result.score = "Undefined"} + this.score = {} + result.score.value = this.calculateTotalScore(dataEntity) + result.score.interpretation = this.interpretTotalScore(result.score) + } else { + result.score = null + } return result } diff --git a/src/utils/DataUtils.js b/src/utils/DataUtils.js index f34b286..8ebc844 100644 --- a/src/utils/DataUtils.js +++ b/src/utils/DataUtils.js @@ -76,7 +76,6 @@ function parseColumnInput(input, dataEntity) { for (let col of dataEntity.transformColumns) { if (col.startsWith(input)) { - console.log(col) return col } } From 0f1aa566e0abc2bfe6cb92d1237589590cd9e772 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Sun, 6 Oct 2024 21:12:04 +0200 Subject: [PATCH 59/65] Use SUS data for testing --- src/app/pages/workspace/Workspace.jsx | 15 ++++++++------- src/services/DataAnalysisService.js | 27 ++++++++++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/app/pages/workspace/Workspace.jsx b/src/app/pages/workspace/Workspace.jsx index 16f900c..2f24a71 100644 --- a/src/app/pages/workspace/Workspace.jsx +++ b/src/app/pages/workspace/Workspace.jsx @@ -9,6 +9,7 @@ import { useWorkspaceContext } from "../../../providers/WorkspaceProvider" import dataService from "../../../services/DataService" import DiagramEntity from "../../../entities/DiagramEntity" import DIAGRAM_TYPE from "../../../constants/DiagramType" +import QUESTIONNAIRE_TYPE from "../../../constants/QuestionnaireType" /** * Main user interface for data analysis. Contains `DataTable`, @@ -20,13 +21,13 @@ export default function Workspace() { // Import test data. Must be deleted in production useEffect(() => { - dataService.importData("tests/test-data/SUS-example.csv") - .then(dataEntity => { - workspace.setDataEntity(dataEntity) - const diagramEntity = new DiagramEntity(DIAGRAM_TYPE.NONE, dataEntity) - workspace.setDiagramEntity(diagramEntity) - updateAll() - }) + dataService.importData("tests/test-data/SUS-example.csv", QUESTIONNAIRE_TYPE.SUS) + .then(dataEntity => { + workspace.setDataEntity(dataEntity) + const diagramEntity = new DiagramEntity(DIAGRAM_TYPE.NONE, dataEntity) + workspace.setDiagramEntity(diagramEntity) + updateAll() + }) }, []) return ( diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 0dd3319..1952c2e 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -33,10 +33,15 @@ class DataAnalysisService { getReport(dataEntity) { + if (!dataEntity || dataEntity.size === 0) { return null } + const result = {} - result.transform = {} - result.userInfo = {} - result.questions = {} + result.dataSize = dataEntity.size + result.columns = { + userInfo: {}, + questions: {}, + transform: {}, + } const getNumericReport = (valueArr) => { return { @@ -50,31 +55,27 @@ class DataAnalysisService { } - if (!dataEntity || dataEntity.size === 0) { return result } - - result.dataSize = dataEntity.size - dataEntity.userInfoColumns.forEach(col => { if (col === "id") { return } const valueArr = dataEntity.col(col) if (typeof dataEntity.loc(0, col) === "number") { - result.userInfo[col] = getNumericReport(valueArr) + result.columns.userInfo[col] = getNumericReport(valueArr) } else { - result.userInfo[col] = getCategoricalReport(valueArr) + result.columns.userInfo[col] = getCategoricalReport(valueArr) } }) dataEntity.questionColumns.forEach(col => { - result.questions[col] = getNumericReport(dataEntity.col(col)) + result.columns.questions[col] = getNumericReport(dataEntity.col(col)) }) dataEntity.transformColumns.forEach(col => { - result.transform[col] = getNumericReport(dataEntity.col(col)) + result.columns.transform[col] = getNumericReport(dataEntity.col(col)) }) if (dataEntity.type !== QUESTIONNAIRE_TYPE.NONE) { - this.score = {} + result.score = {} result.score.value = this.calculateTotalScore(dataEntity) - result.score.interpretation = this.interpretTotalScore(result.score) + result.score.interpretation = this.interpretTotalScore(dataEntity, result.score) } else { result.score = null } From f4d75b902134253a39f3ef9603182687a9cdd6e1 Mon Sep 17 00:00:00 2001 From: SebSch80890 Date: Mon, 7 Oct 2024 09:37:20 +0200 Subject: [PATCH 60/65] Homepage disable function --- src/app/pages/home/Home.css | 7 +++++++ src/app/pages/home/Home.jsx | 23 ++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/app/pages/home/Home.css b/src/app/pages/home/Home.css index d251482..dbb3e3e 100644 --- a/src/app/pages/home/Home.css +++ b/src/app/pages/home/Home.css @@ -122,3 +122,10 @@ padding: 8px; text-align: left; } + +.disabled-section { + pointer-events: none; + opacity: 0.5; + border: 1px solid #ccc; + box-shadow: none; +} diff --git a/src/app/pages/home/Home.jsx b/src/app/pages/home/Home.jsx index a55ab1d..3706ae2 100644 --- a/src/app/pages/home/Home.jsx +++ b/src/app/pages/home/Home.jsx @@ -40,9 +40,10 @@ function Home() { } } } - + //Disable a section (Upload/Mask) if the other is filled + const [selectedSection, setSelectedSection] = useState(null); - // Control if modal is closed + // Control if modal (help/formatinfo) is closed const [isHelpOpen, setIsHelpOpen] = useState(false); const [isUserDataFormatOpen, setIsUserDataFormatOpen] = useState(false); const handleOpenHelp = () => setIsHelpOpen(true); @@ -76,7 +77,7 @@ function Home() { Dein Tool, um schnell und unkompliziert Usability-Daten auzuswerten

- {/* Modal for Info UserData*/} + {/* Info_field*/}

Wir stellen dir hier ein Tool zur Verfügung, um deine Usability-Daten aus Fragebögen auszuwerten. Diese Anwendung ist für User Experience Questionnaire (UEQ), System Usability Scale (SUS), Net Promoter Score (NPS) und RAW Task Load Index geeignet.

@@ -87,10 +88,10 @@ function Home() { {/* Choose Usability Data*/}
{/* Upload Usability Data*/} -
+

Hier kannst du deine Daten hochladen.

1. Wähle deinen Usability-Fragebogen.

- setSelectedSection('upload')}> @@ -101,21 +102,21 @@ function Home() {

2. Lade deine csv-Datei hoch. Für Informationen zum Datei-Format klicke hier.

{/* Datei-Upload-Button*/} - + {setSelectedSection('upload'); fileUploader(event);}} disabled={selectedSection === 'mask'}/>

-
-
- {/* Upload Usability Data*/} +
+ {/* Mask for Usability Data*/}

Hier kannst du über eine Maske Daten eingeben.

1. Wähle deinen Usability-Fragebogen.

- setSelectedSection('mask')}> @@ -127,7 +128,7 @@ function Home() {

- From 569c60cf27006489d507feb967f5a5adc35255b4 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 7 Oct 2024 19:04:31 +0200 Subject: [PATCH 61/65] Add empty workspace factory in provider --- src/providers/WorkspaceProvider.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/providers/WorkspaceProvider.jsx b/src/providers/WorkspaceProvider.jsx index 7cb9896..14206fc 100644 --- a/src/providers/WorkspaceProvider.jsx +++ b/src/providers/WorkspaceProvider.jsx @@ -1,11 +1,25 @@ import { createContext, useContext, useState } from "react"; import WorkspaceEntity from "../entities/WorkspaceEntity"; +import DataEntity from "../entities/DataEntity"; +import DiagramEntity from "../entities/DiagramEntity"; +import DIAGRAM_TYPE from "../constants/DiagramType"; const WorkspaceContext = createContext(null) + +/** + * Provide globally accessible workspace entity. Also work as a factory that generates an + * empty workspace entity. + */ export function WorkspaceProvider({ children }) { const emptyWorkspace = new WorkspaceEntity() + const dataEntity = new DataEntity() + const diagramEntity = new DiagramEntity(DIAGRAM_TYPE.NONE, dataEntity) + + emptyWorkspace.setDataEntity(dataEntity) + emptyWorkspace.setDiagramEntity(diagramEntity) + const [workspace, setWorkspace] = useState(emptyWorkspace) return ( From c12c8bf8d34cdfd6a866b677eff118b11f415d1d Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 7 Oct 2024 19:12:17 +0200 Subject: [PATCH 62/65] Minor change in getRepost --- src/services/DataAnalysisService.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/DataAnalysisService.js b/src/services/DataAnalysisService.js index 1952c2e..75fb871 100644 --- a/src/services/DataAnalysisService.js +++ b/src/services/DataAnalysisService.js @@ -73,9 +73,11 @@ class DataAnalysisService { }) if (dataEntity.type !== QUESTIONNAIRE_TYPE.NONE) { - result.score = {} - result.score.value = this.calculateTotalScore(dataEntity) - result.score.interpretation = this.interpretTotalScore(dataEntity, result.score) + result.score = { + name: dataEntity.type.name, + value: this.calculateTotalScore(dataEntity), + interpretation: this.interpretTotalScore(dataEntity, result.score) + } } else { result.score = null } From eca3334d2241d82aa3f0801618881181a7f555ef Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 7 Oct 2024 19:45:15 +0200 Subject: [PATCH 63/65] Add import button in workspace --- .../workspace/datatables/DataTableTabs.jsx | 11 ++++- .../datatables/Toolbar/ImportBtn.jsx | 43 +++++++++++++++++++ .../datatables/Toolbar/SetColumnsBtn.jsx | 2 + .../datatables/Toolbar/TableToolbar.jsx | 2 + src/services/DataService.js | 20 +++++++-- tests/test-data/SUS-example-2.csv | 41 ++++++++++++++++++ 6 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 src/app/pages/workspace/datatables/Toolbar/ImportBtn.jsx create mode 100644 tests/test-data/SUS-example-2.csv diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx index 8cb402b..9fe0428 100644 --- a/src/app/pages/workspace/datatables/DataTableTabs.jsx +++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx @@ -20,7 +20,16 @@ function _prepareColumns(dataEntity) { headerClassName: `${type}-header` } }; - const userInfo = dataEntity.userInfoColumns.map(prepareCol("userInfo")); + const userInfo = [{ + field: "id", + headerName: "id", + editable: false + }]; + for (let userInfoCol of dataEntity.userInfoColumns) { + if (userInfoCol !== "id") { + userInfo.push(prepareCol("userInfo")(userInfoCol)); + } + } const questions = dataEntity.questionColumns.map(prepareCol("questions")); const transform = dataEntity.transformColumns.map(prepareCol("transform")); return userInfo.concat(transform, questions); diff --git a/src/app/pages/workspace/datatables/Toolbar/ImportBtn.jsx b/src/app/pages/workspace/datatables/Toolbar/ImportBtn.jsx new file mode 100644 index 0000000..b50230c --- /dev/null +++ b/src/app/pages/workspace/datatables/Toolbar/ImportBtn.jsx @@ -0,0 +1,43 @@ +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import { Button } from '@mui/material'; +import { useWorkspaceContext } from '../../../../../providers/WorkspaceProvider'; +import { createRef } from 'react'; +import dataService from '../../../../../services/DataService'; +import { useStatesContext } from '../../../../../providers/StatesProvider'; + +export default function ImportBtn() { + const { workspace } = useWorkspaceContext() + const { updateAll } = useStatesContext() + const inputRef = createRef() + + const handleImport = async (event) => { + const file = event.target.files[0] + if (!file) { return } + const url = URL.createObjectURL(file) + console.log(url) + const dataEntity = await dataService.importData(url) + workspace.setDataEntity(dataEntity) + updateAll() + } + + const handleBtnClick = () => { + inputRef.current.click() + } + + return ( +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx b/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx index 49d6851..4918d86 100644 --- a/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx +++ b/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { Box, Button, + Divider, Popover, Stack, TextField, @@ -89,6 +90,7 @@ export default function SetColumnsBtn() { onChange={(event) => setNewTransform(event.target.value.trim())} /> + + diff --git a/src/services/DataService.js b/src/services/DataService.js index 717ffd3..7a6bc9f 100644 --- a/src/services/DataService.js +++ b/src/services/DataService.js @@ -7,12 +7,24 @@ class DataService { async importData(filePath, type=QUESTIONNAIRE_TYPE.NONE) { const data = await d3.csv(filePath, d3.autoType) - return new DataEntity(type, data) + const dataEntity = new DataEntity(type, data) + this.generateId(dataEntity) + return dataEntity + } + + + /** + * Generate unique id for each row of data. This is required by MUI DataGrid. + * @param {DataEntity} dataEntity + */ + generateId(dataEntity) { + if (dataEntity.userInfoColumns.includes("id")) { return } + dataEntity.addUserInfoColumns("id") + for (let i = 0; i < dataEntity.size; i++) { + dataEntity.setValue(i, "id", i + 1) + } } - // exportData(filePath, dataEntity) { - // writeFileSync(filePath, this.stringify(dataEntity)) - // } /** * Return a string of CSV format representing the data entity. diff --git a/tests/test-data/SUS-example-2.csv b/tests/test-data/SUS-example-2.csv new file mode 100644 index 0000000..d452034 --- /dev/null +++ b/tests/test-data/SUS-example-2.csv @@ -0,0 +1,41 @@ +fake_id,age,gender,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10 +1,37,M,4,3,3,2,4,3,4,3,4,1 +2,41,M,5,2,5,2,3,3,5,1,5,3 +3,12,F,5,2,5,1,4,1,4,1,5,1 +4,29,M,4,1,4,2,4,2,4,2,5,1 +5,50,F,2,3,1,4,2,4,1,3,4,4 +6,19,M,5,2,5,1,3,2,5,1,5,1 +7,36,F,2,3,4,2,4,3,4,4,2,3 +8,61,M,4,3,4,4,1,3,2,4,3,4 +9,45,M,5,3,2,3,5,3,5,3,5,2 +10,25,M,5,2,5,1,5,1,5,2,3,2 +11,38,M,3,3,4,1,5,3,4,3,5,1 +12,56,F,1,5,1,5,2,5,1,4,1,4 +13,33,M,3,2,5,2,4,2,5,2,5,1 +14,25,M,3,2,4,2,5,1,3,1,5,1 +15,28,F,4,3,5,1,5,2,3,1,3,2 +16,40,M,5,1,4,2,5,2,3,3,5,3 +17,20,F,3,2,5,2,4,2,4,3,5,1 +18,29,F,3,3,2,2,5,3,4,2,2,3 +19,27,F,2,1,4,2,4,2,4,2,3,2 +20,37,M,4,2,5,3,3,2,3,3,3,3 +21,29,M,5,2,4,2,4,2,5,2,3,3 +22,34,M,4,2,3,1,4,3,3,1,4,3 +23,44,M,3,4,3,2,3,3,4,3,5,2 +24,45,M,5,3,4,3,5,3,3,2,4,3 +25,52,F,1,4,3,3,1,4,2,4,2,4 +26,17,F,5,2,5,2,5,1,5,2,5,2 +27,23,M,3,2,4,1,5,1,5,2,5,1 +28,26,F,3,1,5,3,5,2,3,3,5,2 +29,49,M,2,3,5,3,3,3,1,2,4,2 +30,36,F,4,3,4,2,2,2,5,2,2,3 +31,28,M,3,1,3,1,4,2,5,1,3,2 +32,35,F,5,3,3,2,4,3,2,2,2,3 +33,19,F,5,2,4,2,5,2,5,1,5,1 +34,20,F,5,2,5,1,3,2,5,1,5,2 +35,28,F,5,3,3,3,3,2,5,2,3,2 +36,46,M,3,3,2,3,2,3,3,3,5,2 +37,37,F,4,2,3,4,5,3,3,3,3,3 +38,30,M,5,2,3,1,4,2,3,3,5,2 +39,22,F,4,3,4,2,4,3,3,3,5,1 +40,26,F,3,2,4,2,2,1,3,1,3,2 From 6d0fbbe8d8035928a51baac8cd5ad89d671d66b3 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 7 Oct 2024 20:45:29 +0200 Subject: [PATCH 64/65] Add qustionnaire type selecter --- .../datatables/Toolbar/SetColumnsBtn.jsx | 84 ++++++++++++++++--- .../workspace/modification/Modification.jsx | 13 +-- src/entities/DataEntity.js | 19 +++-- tests/test-data/SUS-example-2.csv | 12 +-- 4 files changed, 98 insertions(+), 30 deletions(-) diff --git a/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx b/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx index 4918d86..17c44f6 100644 --- a/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx +++ b/src/app/pages/workspace/datatables/Toolbar/SetColumnsBtn.jsx @@ -3,13 +3,19 @@ import { Box, Button, Divider, + FormControl, + InputLabel, + MenuItem, Popover, + Select, Stack, TextField, } from "@mui/material"; import { useWorkspaceContext } from "../../../../../providers/WorkspaceProvider"; import { useStatesContext } from '../../../../../providers/StatesProvider'; import TableChartIcon from '@mui/icons-material/TableChart'; +import { parseColumnInput } from '../../../../../utils/DataUtils'; +import QUESTIONNAIRE_TYPE from '../../../../../constants/QuestionnaireType'; @@ -21,14 +27,28 @@ export default function SetColumnsBtn() { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const id = open ? 'simple-popover' : undefined; - const handleClick = (event) => setAnchorEl(event.currentTarget); - const handleClose = () => setAnchorEl(null); - + const [questionNr, setQuestionNr] = useState(""); const [newUserInfo, setNewUserInfo] = useState(""); const [newTransform, setNewTransform] = useState(""); const [deleteColumn, setDeleteColumn] = useState(""); - + const [qtnType, setQtnType] = useState(dataEntity.type); + + + const clearStates = () => { + setQuestionNr("") + setNewUserInfo("") + setNewTransform("") + setDeleteColumn("") + setQtnType(dataEntity.type) + }; + + const handleClick = (event) => setAnchorEl(event.currentTarget); + const handleClose = () => { + setAnchorEl(null); + clearStates(); + } + const applyChanges = () => { if (/^\d+$/.test(questionNr)) { dataEntity.setNumOfQuestions(parseInt(questionNr)); @@ -42,7 +62,9 @@ export default function SetColumnsBtn() { if (deleteColumn) { dataEntity.deleteColumns(deleteColumn); } + dataEntity.setType(qtnType); updateTable(); + handleClose(); }; return ( @@ -73,11 +95,18 @@ export default function SetColumnsBtn() { > - setQuestionNr(event.target.value.trim())} - /> + + setQtnType(event.target.value)} + /> + setQuestionNr(event.target.value.trim())} + /> + setDeleteColumn(event.target.value.trim())} + onChange={(event) => setDeleteColumn(parseColumnInput(event.target.value, dataEntity))} /> @@ -102,4 +131,39 @@ export default function SetColumnsBtn() {
); +} + + +const availableQtnTypes = Object.values(QUESTIONNAIRE_TYPE); +availableQtnTypes.shift() + +function QtnTypeSelect({ currentType, onChange }) { + return ( + <> + + Set questionnaire type + + + + ); } \ No newline at end of file diff --git a/src/app/pages/workspace/modification/Modification.jsx b/src/app/pages/workspace/modification/Modification.jsx index cd8ee29..e7bdbd3 100644 --- a/src/app/pages/workspace/modification/Modification.jsx +++ b/src/app/pages/workspace/modification/Modification.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react" import { useStatesContext } from "../../../../providers/StatesProvider" import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider" -import { Box, Button, DialogActions, Divider, FormControl, MenuItem, Select, TextField } from "@mui/material" +import { Box, Button, DialogActions, Divider, FormControl, InputLabel, MenuItem, Select, TextField } from "@mui/material" import DIAGRAM_TYPE from "../../../../constants/DiagramType" import OptionFields from "./OptionFields" import DiagramSettings from "./DiagramSettings" @@ -47,7 +47,7 @@ function Modification() { updateDiagram() } - const confirm = (event) => { + const confirm = () => { Object.keys(options).map(opt => { let value = null if (opt === "x" || opt === "y") { value = parseColumnInput(options[opt], dataEntity) } @@ -62,11 +62,13 @@ function Modification() {
Diagram Type - + + Choose type + Options diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js index 5c8f2e5..000f091 100644 --- a/src/entities/DataEntity.js +++ b/src/entities/DataEntity.js @@ -38,9 +38,9 @@ export default class DataEntity { * @param {number} numOfQuestions */ addQuestions(numOfQuestions=1) { - if (this.type !== QUESTIONNAIRE_TYPE.NONE) { - throw new QuestionnaireTypeError("Only NONE-type data allows adding new questions.") - } + // if (this.type !== QUESTIONNAIRE_TYPE.NONE) { + // throw new QuestionnaireTypeError("Only NONE-type data allows adding new questions.") + // } for (let i = 0; i < numOfQuestions; i++) { const newCol = `Q${this.numOfQuestions + 1}` this.columns.questions.push(newCol) @@ -53,9 +53,9 @@ export default class DataEntity { * @param {number} numOfQuestions */ deleteQuestions(numOfQuestions=1) { - if (this.type !== QUESTIONNAIRE_TYPE.NONE) { - throw new QuestionnaireTypeError("Only NONE-type data allows deleting questions.") - } + // if (this.type !== QUESTIONNAIRE_TYPE.NONE) { + // throw new QuestionnaireTypeError("Only NONE-type data allows deleting questions.") + // } for (let i = 0; i < numOfQuestions; i++) { const deletedCol = this.columns.questions.pop() this.data.forEach(row => delete row[deletedCol]) @@ -63,9 +63,9 @@ export default class DataEntity { } setNumOfQuestions(numOfQuestions) { - if (this.type !== QUESTIONNAIRE_TYPE.NONE) { - throw new QuestionnaireTypeError("Only NONE-type data allows setting number of questions.") - } + // if (this.type !== QUESTIONNAIRE_TYPE.NONE) { + // throw new QuestionnaireTypeError("Only NONE-type data allows setting number of questions.") + // } const difference = numOfQuestions - this.numOfQuestions if (difference === 0) { return } if (difference > 0) { this.addQuestions(difference) } @@ -214,6 +214,7 @@ export default class DataEntity { setType(type) { this.type = type + this.setNumOfQuestions(type.numOfQuestions) } diff --git a/tests/test-data/SUS-example-2.csv b/tests/test-data/SUS-example-2.csv index d452034..868f29f 100644 --- a/tests/test-data/SUS-example-2.csv +++ b/tests/test-data/SUS-example-2.csv @@ -1,7 +1,7 @@ fake_id,age,gender,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10 -1,37,M,4,3,3,2,4,3,4,3,4,1 -2,41,M,5,2,5,2,3,3,5,1,5,3 -3,12,F,5,2,5,1,4,1,4,1,5,1 +0,37,M,4,3,3,2,4,3,4,3,4,1 +0,41,M,5,2,5,2,3,3,5,1,5,3 +0,12,F,5,2,5,1,4,1,4,1,5,1 4,29,M,4,1,4,2,4,2,4,2,5,1 5,50,F,2,3,1,4,2,4,1,3,4,4 6,19,M,5,2,5,1,3,2,5,1,5,1 @@ -9,10 +9,10 @@ fake_id,age,gender,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10 8,61,M,4,3,4,4,1,3,2,4,3,4 9,45,M,5,3,2,3,5,3,5,3,5,2 10,25,M,5,2,5,1,5,1,5,2,3,2 -11,38,M,3,3,4,1,5,3,4,3,5,1 +122,38,M,3,3,4,1,5,3,4,3,5,1 12,56,F,1,5,1,5,2,5,1,4,1,4 13,33,M,3,2,5,2,4,2,5,2,5,1 -14,25,M,3,2,4,2,5,1,3,1,5,1 +43,25,M,3,2,4,2,5,1,3,1,5,1 15,28,F,4,3,5,1,5,2,3,1,3,2 16,40,M,5,1,4,2,5,2,3,3,5,3 17,20,F,3,2,5,2,4,2,4,3,5,1 @@ -36,6 +36,6 @@ fake_id,age,gender,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10 35,28,F,5,3,3,3,3,2,5,2,3,2 36,46,M,3,3,2,3,2,3,3,3,5,2 37,37,F,4,2,3,4,5,3,3,3,3,3 -38,30,M,5,2,3,1,4,2,3,3,5,2 +30,30,M,5,2,3,1,4,2,3,3,5,2 39,22,F,4,3,4,2,4,3,3,3,5,1 40,26,F,3,2,4,2,2,1,3,1,3,2 From 7d0cb97ec955c67a807ba8aaa73429ae79feb9b6 Mon Sep 17 00:00:00 2001 From: Roy_Xu Date: Mon, 7 Oct 2024 21:28:46 +0200 Subject: [PATCH 65/65] Preparation for CD --- README.md | 2 ++ vite.config.js | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 15932d0..d26ab41 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## About the app +Try it out with this link: [https://UniRegensburg.github.io/mme-sose24-statistics/](https://UniRegensburg.github.io/mme-sose24-statistics/). + ## Setup To install the application, run diff --git a/vite.config.js b/vite.config.js index 5a33944..fd8f4d6 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,4 +4,5 @@ import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + base: "/mme-sose24-statistics/" })