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 + React
+
+
setCount((count) => count + 1)}>
+ count is {count}
+
+
+ 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 */}
+
+
+ Go to workspace
+
+
+
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
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
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) => (
+ setActiveTab(index)}
+ className={activeTab === index ? 'active-tab' : ''}
+ >
+ Data Table {index + 1} {}
+
+ ))}
+
+
+
+ {dataTables.length > 0 ? (
+
+
+
+ {Object.keys(dataTables[activeTab][0]).map((columnName, i) => (
+ {columnName}
+ ))}
+
+
+
+ {dataTables[activeTab].map((row, i) => (
+
+ {Object.values(row).map((value, j) => (
+ {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) => (
- setActiveTab(index)}
- className={activeTab === index ? 'active-tab' : ''}
- >
- Data Table {index + 1} {}
-
- ))}
-
-
-
- {dataTables.length > 0 ? (
-
-
-
- {Object.keys(dataTables[activeTab][0]).map((columnName, i) => (
- {columnName}
- ))}
-
-
-
- {dataTables[activeTab].map((row, i) => (
-
- {Object.values(row).map((value, j) => (
- {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.
+
+ Schließen
+
+
+ Darkmode-Reminder
+ {/*Logo*/}
- Vite + React
-
-
setCount((count) => count + 1)}>
- count is {count}
-
-
- 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.
+
+
+
+ 1
+ 25
+ M
+
+
+ 2
+ 30
+ W
+
+
+ 3
+ 22
+ M
+
+
+
+
+ Schließen
+
+
+
+ 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!
+
+ -- Bitte wählen --
+ User Experience Questionnaire (UEQ)
+ System Usability Scale (SUS)
+ Net Promoter Score (NPS)
+ RAW Task Load Index
+
+
+
+
+ Upload File
+
+
+
+
+ {/* 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!
+
+ -- Bitte wählen --
+ User Experience Questionnaire (UEQ)
+ System Usability Scale (SUS)
+ Net Promoter Score (NPS)
+ RAW Task Load Index
+
+
+
+
+ Use Mask for Data
+
+
+
+
+ {/* use Link for redirecting */}
+
-
- Go to workspace
-
+
+ Start to Analyse
+
-
-
+
- Click on the Vite and React logos to learn more
+ Impressum!!!!!!!!!!!!!!!!!!!!!
+
+
+{/* setCount((count) => count + 1)}>
+ Upload User-Data {count}
+
+
+ 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.
-
- Schließen
-
-
- 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.
+
+ Schließen
+
+
+ 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.
-
-
-
- 1
- 25
- M
-
-
- 2
- 30
- W
-
-
- 3
- 22
- M
-
-
-
-
- Schließen
-
-
+
+ Datenformat
+
+ Zugelassen sind nur csv-Dateien. Diese müssen für die Analyse im folgenden Format vorliegen.
+
+
+
+ 1
+ 25
+ M
+
+
+ 2
+ 30
+ W
+
+
+ 3
+ 22
+ M
+
+
+
+
+ Schließen
+
+
- Upload demographic data
-
+ 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!
-
+
+
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!
+
-- Bitte wählen --
User Experience Questionnaire (UEQ)
System Usability Scale (SUS)
@@ -98,15 +105,15 @@ function Home() {
-
+
Upload File
-
-
-
-
- {/* 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!
-
+
+
+
+
+ {/* 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!
+
-- Bitte wählen --
User Experience Questionnaire (UEQ)
System Usability Scale (SUS)
@@ -115,11 +122,11 @@ function Home() {
-
+
Use Mask for Data
-
-
-
+
+
+
{/* use Link for redirecting */}
@@ -128,13 +135,13 @@ function Home() {
Start to Analyse
-
+
Impressum!!!!!!!!!!!!!!!!!!!!!
-{/* setCount((count) => count + 1)}>
+ {/* setCount((count) => count + 1)}>
Upload User-Data {count}
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 (
+
+
+ set columns
+
+
+ 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 (
+
+ }>
+ my report
+
+
+
+
+ {JSON.stringify(report)}
+
+
+
+ Close
+
+
+
+ )
+}
+
+
+// ---------- 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 (
}
onClick={handleClick}
- >
+ >
set columns
+ >
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
+
+
+
+ -
+ {availablDiagTypes.map((type, index) => (
+ {type.name}
+ ))}
+
+
+ Options
+
+ {allOptions.map((opt, index) => (
+
+ ))}
+
+
+
+
+
+
+ Apply changes
+
+
)
}
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
+
+ Settings
+
+
+
-
+
Apply changes
diff --git a/src/app/pages/workspace/modification/OptionFields.jsx b/src/app/pages/workspace/modification/OptionFields.jsx
index b87ef3f..2fb4c5a 100644
--- a/src/app/pages/workspace/modification/OptionFields.jsx
+++ b/src/app/pages/workspace/modification/OptionFields.jsx
@@ -1,4 +1,4 @@
-import { TextField } from "@mui/material";
+import { TextField, Typography } from "@mui/material";
import { useEffect } from "react";
@@ -8,11 +8,17 @@ export default function OptionFields({
requiredOptions,
onChange
}) {
+ if (allOptions.length === 0) {
+ return (
+
Please select a diagram type.
+ )
+ }
return (
<>
{allOptions.map((opt, index) => (
{
- const plotOptions = commonPlotOptions(options)
+ plotOptions: (data, settings, options) => {
+ const plotOptions = commonPlotSettings(settings)
plotOptions.marks = [
Plot.ruleY([0]),
Plot.rectY(data, Plot.binX({y: "count"}, {x: options.x}))
@@ -62,14 +43,14 @@ DIAGRAM_TYPE.HIST = {
DIAGRAM_TYPE.SCATTER = {
name: "Scatter",
- options: ["x", "y"].concat(COMMON_OPTIONS),
+ options: ["x", "y"],
requiredOptions: ["x", "y"],
- plotOptions: (data, options) => {
- const plotOptions = commonPlotOptions(options)
+ plotOptions: (data, settings, options) => {
+ const plotOptions = commonPlotSettings(settings)
plotOptions.marks = [
- Plot.ruleY([0]),
- Plot.dot(data, {x: options.x, y: options.y})
- ]
+ Plot.ruleY([0]),
+ Plot.dot(data, {x: options.x, y: options.y, fill: options.y})
+ ]
return plotOptions
},
}
diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js
index e2c253a..64755b7 100644
--- a/src/entities/DiagramEntity.js
+++ b/src/entities/DiagramEntity.js
@@ -1,3 +1,4 @@
+import { DIAGRAM_SETTING } from "../constants/DiagramSetting";
import DIAGRAM_TYPE from "../constants/DiagramType";
import { DiagramTypeError } from "../exceptions/DataExceptions";
import DataEntity from "./DataEntity";
@@ -13,7 +14,8 @@ export default class DiagramEntity {
this.type = type
this.linkedData = linkedData
this.options = {}
- this._setDefaultOptions()
+ this.settings = {}
+ this._setDefaultSettings()
Object.keys(options).forEach(key => {
this.setOption(key, options[key])
})
@@ -22,9 +24,21 @@ export default class DiagramEntity {
/**
* 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"
+ _setDefaultSettings() {
+ Object.values(DIAGRAM_SETTING.BOOL).forEach(setting => {
+ this.setSetting(setting.name, setting.default)
+ })
+ Object.values(DIAGRAM_SETTING.SELECT).forEach(setting => {
+ this.setSetting(setting.name, setting.default)
+ })
+ }
+
+ setSetting(key, value) {
+ if (!value) {
+ delete this.options[key]
+ return
+ }
+ this.settings[key] = value
}
@@ -86,7 +100,8 @@ export default class DiagramEntity {
*/
generatePlotOptions() {
this.checkPlotability()
- return this.type.plotOptions(this.linkedData.data, this.options)
+ console.log( JSON.stringify( this.type.plotOptions(this.linkedData.data, this.settings, this.options) ) )
+ return this.type.plotOptions(this.linkedData.data, this.settings, this.options)
}
/**
@@ -113,8 +128,6 @@ export default class DiagramEntity {
get requiredOptions() { return this.type.requiredOptions }
- getOption(key) {
- return this.options[key]
- }
+ getOption(key) { return this.options[key] }
}
\ No newline at end of file
From b2cb70ba2989c64ebe710a935fcfeeb166b8423d Mon Sep 17 00:00:00 2001
From: Roy_Xu
Date: Sat, 5 Oct 2024 14:32:38 +0200
Subject: [PATCH 50/65] Connect setting component to workspace
---
.../modification/DiagramSettings.jsx | 24 +++++++++++++++----
src/entities/DiagramEntity.js | 5 ++--
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/src/app/pages/workspace/modification/DiagramSettings.jsx b/src/app/pages/workspace/modification/DiagramSettings.jsx
index be2c014..326a52b 100644
--- a/src/app/pages/workspace/modification/DiagramSettings.jsx
+++ b/src/app/pages/workspace/modification/DiagramSettings.jsx
@@ -1,14 +1,25 @@
import { Checkbox, FormControl, FormControlLabel, FormGroup } from "@mui/material";
import { DIAGRAM_SETTING } from "../../../../constants/DiagramSetting";
+import { useWorkspaceContext } from "../../../../providers/WorkspaceProvider";
+import { useStatesContext } from "../../../../providers/StatesProvider";
+import { useEffect } from "react";
const boolSettingList = Object.values(DIAGRAM_SETTING.BOOL)
-export default function DiagramSettings(
-
-) {
+export default function DiagramSettings() {
+ const { updateDiagram, updateModification } = useStatesContext()
+ const { workspace } = useWorkspaceContext()
+ const diagramEntity = workspace.diagramEntity
+
+ const onCheckboxChange = (event) => {
+ const { name, checked } = event.target
+ diagramEntity.setSetting(name, checked)
+ updateDiagram()
+ }
+
return (
@@ -16,7 +27,12 @@ export default function DiagramSettings(
{boolSettingList.map(setting => (
+
}
label={setting.name}
/>
diff --git a/src/entities/DiagramEntity.js b/src/entities/DiagramEntity.js
index 64755b7..66f785b 100644
--- a/src/entities/DiagramEntity.js
+++ b/src/entities/DiagramEntity.js
@@ -34,7 +34,7 @@ export default class DiagramEntity {
}
setSetting(key, value) {
- if (!value) {
+ if (value === null || value === undefined) {
delete this.options[key]
return
}
@@ -100,7 +100,6 @@ export default class DiagramEntity {
*/
generatePlotOptions() {
this.checkPlotability()
- console.log( JSON.stringify( this.type.plotOptions(this.linkedData.data, this.settings, this.options) ) )
return this.type.plotOptions(this.linkedData.data, this.settings, this.options)
}
@@ -130,4 +129,6 @@ export default class DiagramEntity {
getOption(key) { return this.options[key] }
+ getSetting(key) { return this.settings[key]}
+
}
\ No newline at end of file
From aca9992942d825cd63e488585819ee821c01d8fa Mon Sep 17 00:00:00 2001
From: Roy_Xu
Date: Sat, 5 Oct 2024 15:07:37 +0200
Subject: [PATCH 51/65] Fix bug
---
src/constants/DiagramType.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/constants/DiagramType.js b/src/constants/DiagramType.js
index 534cac0..3ee7809 100644
--- a/src/constants/DiagramType.js
+++ b/src/constants/DiagramType.js
@@ -49,7 +49,7 @@ DIAGRAM_TYPE.SCATTER = {
const plotOptions = commonPlotSettings(settings)
plotOptions.marks = [
Plot.ruleY([0]),
- Plot.dot(data, {x: options.x, y: options.y, fill: options.y})
+ Plot.dot(data, {x: options.x, y: options.y})
]
return plotOptions
},
From 9cce24593951fe0657b4d02a48cc0ece7fb420db Mon Sep 17 00:00:00 2001
From: Roy_Xu
Date: Sat, 5 Oct 2024 16:21:07 +0200
Subject: [PATCH 52/65] Add data settings
---
.../workspace/datatables/DataTableTabs.jsx | 2 +-
.../workspace/datatables/TableToolbar.jsx | 57 ++++++++++++++++++-
src/entities/DataEntity.js | 8 +--
3 files changed, 59 insertions(+), 8 deletions(-)
diff --git a/src/app/pages/workspace/datatables/DataTableTabs.jsx b/src/app/pages/workspace/datatables/DataTableTabs.jsx
index 78df8b9..84db184 100644
--- a/src/app/pages/workspace/datatables/DataTableTabs.jsx
+++ b/src/app/pages/workspace/datatables/DataTableTabs.jsx
@@ -23,7 +23,7 @@ function _prepareColumns(dataEntity) {
const userInfo = dataEntity.userInfoColumns.map(prepareCol);
const questions = dataEntity.questionColumns.map(prepareCol);
const transform = dataEntity.transformColumns.map(prepareCol);
- return userInfo.concat(questions, transform);
+ return userInfo.concat(transform, questions);
}
/**
diff --git a/src/app/pages/workspace/datatables/TableToolbar.jsx b/src/app/pages/workspace/datatables/TableToolbar.jsx
index 722eccf..30658cb 100644
--- a/src/app/pages/workspace/datatables/TableToolbar.jsx
+++ b/src/app/pages/workspace/datatables/TableToolbar.jsx
@@ -6,6 +6,8 @@ import { Box,
DialogContent,
DialogContentText,
Popover,
+ Stack,
+ TextField,
Typography
} from "@mui/material";
import { GridToolbarColumnsButton, GridToolbarContainer, GridToolbarExport } from "@mui/x-data-grid"
@@ -70,6 +72,7 @@ function MyReportBtn() {
// ---------- 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)
@@ -77,7 +80,26 @@ function SetColumnsBtn() {
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 applyChanges = () => {
+ if (/^\d+$/.test(questionNr)) {
+ dataEntity.setNumOfQuestions(parseInt(questionNr))
+ }
+ if (newUserInfo) {
+ dataEntity.addUserInfoColumns(newUserInfo)
+ }
+ if (newTransform) {
+ dataEntity.addTransformColumns(newTransform)
+ }
+ if (deleteColumn) {
+ dataEntity.deleteColumns(deleteColumn)
+ }
+ updateTable()
+ }
return (
@@ -90,7 +112,7 @@ function SetColumnsBtn() {
startIcon={ }
onClick={handleClick}
>
- set columns
+ Data settings
- Your code here
+
+
+ setQuestionNr(event.target.value.trim())}
+ />
+
+ setNewUserInfo(event.target.value.trim())}
+ />
+ setNewTransform(event.target.value.trim())}
+ />
+
+ setDeleteColumn(event.target.value.trim())}
+ />
+
+ confirm
+
+
+
+
)
diff --git a/src/entities/DataEntity.js b/src/entities/DataEntity.js
index cce9e8b..77d3e14 100644
--- a/src/entities/DataEntity.js
+++ b/src/entities/DataEntity.js
@@ -61,14 +61,14 @@ export default class DataEntity {
}
}
- setNumOfQUestions(numOfQuestions) {
+ 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) }
+ if (difference > 0) { this.addQuestions(difference) }
+ else { this.deleteQuestions(-difference) }
}
/**
@@ -108,11 +108,11 @@ export default class DataEntity {
let columns = expressions.map(col => `T:${col}`)
columns = columns.filter(col => !this.transformColumns.includes(col))
- 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])
})
+ this.columns.transform = this.columns.transform.concat(columns)
}
/**
From 9eb65c4aebd4a0951b0ec6ba2519f428c6287ff6 Mon Sep 17 00:00:00 2001
From: SebSch80890
Date: Sat, 5 Oct 2024 16:43:05 +0200
Subject: [PATCH 53/65] Update Home plus Import csv
---
package-lock.json | 1 +
public/Logo_UsabilityAnalyzer.jpeg | Bin 0 -> 58829 bytes
src/app/pages/home/Home.css | 10 ++---
src/app/pages/home/Home.jsx | 61 ++++++++++++++++++++---------
4 files changed, 49 insertions(+), 23 deletions(-)
create mode 100644 public/Logo_UsabilityAnalyzer.jpeg
diff --git a/package-lock.json b/package-lock.json
index bab8b86..536c902 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2382,6 +2382,7 @@
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
"dependencies": {
"d3-array": "3",
"d3-axis": "3",
diff --git a/public/Logo_UsabilityAnalyzer.jpeg b/public/Logo_UsabilityAnalyzer.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..4b251b19711a5be422904942ef34d37c3a1c150c
GIT binary patch
literal 58829
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%0ABU_`4fM74ZwrLa@4GG(m
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