diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..777263d3
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,28 @@
+# Copyright 2026 Open Reaction Database Project Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Keep local dev artifacts out of the image; the Dockerfile installs deps
+# itself and a macOS-built node_modules contains the wrong platform's
+# rollup binary (see Dockerfile comment about npm cli#4828).
+**/node_modules/
+**/dist/
+**/.venv/
+**/__pycache__/
+**/.pytest_cache/
+**/.ruff_cache/
+**/.DS_Store
+**/dump.rdb
+.git/
+.idea/
+.vscode/
diff --git a/.gitignore b/.gitignore
index 67f15e11..de713e4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,7 +15,7 @@ __pycache__/
.venv/
# Redis dumps.
-dump.rdb
+**/dump.rdb
# vim swap files
*.sw[a-z]
# build output
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 00000000..5b811e53
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20.19.4
\ No newline at end of file
diff --git a/README.md b/README.md
index cccd0cfb..b8bd72ec 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ Web interface and FastAPI server for the [Open Reaction Database](https://open-r
## Project layout
-- **`app/`** — Vue single-page application (frontend), built with Vite
- - `src/components/` — reusable Vue components
+- **`app/`** — React single-page application (frontend), built with Vite
+ - `src/components/` — reusable React components
- `src/views/` — routed pages (browse, search, dataset/reaction views)
- `src/router/`, `src/styles/`, `src/utils/`, `src/assets/`
- **`ord_interface/`** — Python backend
@@ -14,7 +14,7 @@ Web interface and FastAPI server for the [Open Reaction Database](https://open-r
- `visualization/` — Jinja filters and helpers for rendering reactions and molecules
- `editor/` — legacy reaction-submission UI (being removed)
-The Vue frontend talks to the FastAPI server, so both processes need to be running for the UI to work. On Windows, use WSL.
+The React frontend talks to the FastAPI server, so both processes need to be running for the UI to work. On Windows, use WSL.
## Local development
@@ -57,6 +57,7 @@ docker compose up
#### Option B: Local (in-process testing database)
+In your local conda environment
```shell
# Redis (the API uses it for search-task state); leaves it daemonized.
redis-server --daemonize yes
@@ -73,9 +74,9 @@ ORD_INTERFACE_TESTING=TRUE uv run uvicorn ord_interface.api.main:app --port 5000
> proxy target in `app/vite.config.js`. Uvicorn binds `127.0.0.1` only, so
> `http://127.0.0.1:5000/...` will reach uvicorn regardless.
-### 3. Run the Vue SPA
+### 3. Run the React SPA
-Download [Ketcher v2.5.1](https://github.com/epam/ketcher/releases/download/v2.5.1/ketcher-standalone-2.5.1.zip), extract it, and place the `standalone/` directory's contents inside `./app/src/ketcher/` (so that `app/src/ketcher/static/` and `app/src/ketcher/templates/` exist). Then:
+Download [Ketcher v2.5.1](https://github.com/epam/ketcher/releases/download/v2.5.1/ketcher-standalone-2.5.1.zip), extract it, and place the `standalone/` directory's contents inside `./app/public/ketcher/` (so that `app/public/ketcher/static/` and `app/public/ketcher/asset-manifest.json` exist). Then:
```shell
cd app
@@ -99,4 +100,4 @@ Production deployment is managed via Pulumi/ECS in the [`ord-infrastructure`](ht
### Major changes (new message types, etc.)
-The Vue app needs to be updated to surface new fields. Please discuss any such proposals with the ORD team before opening a PR.
+The React app needs to be updated to surface new fields. Please discuss any such proposals with the ORD team before opening a PR.
diff --git a/app/.browserslistrc b/app/.browserslistrc
deleted file mode 100644
index dc3bc09a..00000000
--- a/app/.browserslistrc
+++ /dev/null
@@ -1,4 +0,0 @@
-> 1%
-last 2 versions
-not dead
-not ie 11
diff --git a/app/.gitignore b/app/.gitignore
index 403adbc1..a547bf36 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,21 +1,22 @@
-.DS_Store
-node_modules
-/dist
-
-
-# local env files
-.env.local
-.env.*.local
-
-# Log files
+# 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
-.vscode
+.DS_Store
*.suo
*.ntvs*
*.njsproj
diff --git a/app/.prettierignore b/app/.prettierignore
index 46e52eac..f8bf35d5 100644
--- a/app/.prettierignore
+++ b/app/.prettierignore
@@ -1,5 +1,4 @@
dist/
node_modules/
public/
-src/ketcher/
package-lock.json
diff --git a/app/README.md b/app/README.md
index cf8e307c..bccaec74 100644
--- a/app/README.md
+++ b/app/README.md
@@ -1,29 +1,73 @@
-# app
+# React + TypeScript + Vite
-## Project setup
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
-```
-npm install
-```
+Currently, two official plugins are available:
-### Compiles and hot-reloads for development
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-```
-npm run serve
-```
+## React Compiler
-### Compiles and minifies for production
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
-```
-npm run build
-```
+## Expanding the ESLint configuration
-### Lints and fixes files
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
-```
-npm run lint
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+]);
```
-### Customize configuration
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
-See [Configuration Reference](https://cli.vuejs.org/config/).
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x';
+import reactDom from 'eslint-plugin-react-dom';
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+]);
+```
diff --git a/app/eslint.config.js b/app/eslint.config.js
index 791d2ef4..48b631a5 100644
--- a/app/eslint.config.js
+++ b/app/eslint.config.js
@@ -15,23 +15,25 @@
*/
import js from '@eslint/js';
-import vue from 'eslint-plugin-vue';
import globals from 'globals';
+import reactHooks from 'eslint-plugin-react-hooks';
+import reactRefresh from 'eslint-plugin-react-refresh';
+import tseslint from 'typescript-eslint';
+import { defineConfig, globalIgnores } from 'eslint/config';
-export default [
- {
- ignores: ['dist/', 'node_modules/', 'src/ketcher/'],
- },
- js.configs.recommended,
- ...vue.configs['flat/essential'],
+export default defineConfig([
+ globalIgnores(['dist/', 'node_modules/', 'public/ketcher/']),
{
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
languageOptions: {
- ecmaVersion: 2022,
- sourceType: 'module',
- globals: {
- ...globals.browser,
- ...globals.node,
- },
+ ecmaVersion: 2020,
+ globals: globals.browser,
},
rules: {
// GitHub Actions sets CI=true; ESLint itself never sets NODE_ENV, so the
@@ -39,7 +41,6 @@ export default [
// during local iteration.
'no-console': process.env.CI ? 'warn' : 'off',
'no-debugger': process.env.CI ? 'warn' : 'off',
- 'no-unused-vars': 'warn',
},
},
-];
+]);
diff --git a/app/index.html b/app/index.html
index 5ec1297f..9a3291ec 100644
--- a/app/index.html
+++ b/app/index.html
@@ -15,21 +15,20 @@
limitations under the License.
-->
-
+
-
+
-
Open Reaction Database
-
+
diff --git a/app/jsconfig.json b/app/jsconfig.json
deleted file mode 100644
index b6bd4c8d..00000000
--- a/app/jsconfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "module": "esnext",
- "baseUrl": "./",
- "moduleResolution": "node",
- "paths": {
- "@/*": ["src/*"]
- },
- "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
- }
-}
diff --git a/app/package-lock.json b/app/package-lock.json
index 26ad0f8c..be750f9e 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -1,568 +1,394 @@
{
"name": "app",
- "version": "0.1.0",
+ "version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "app",
- "version": "0.1.0",
- "dependencies": {
- "buffer": "^6.0.3",
+ "version": "0.0.0",
+ "dependencies": {
+ "@headlessui/react": "^2.2.8",
+ "@heroicons/react": "^2.2.0",
+ "@hookform/resolvers": "^5.2.2",
+ "@tanstack/react-query": "^5.90.2",
+ "@types/d3": "^7.4.3",
+ "@types/react-router-dom": "^5.3.3",
+ "axios": "^1.12.2",
"d3": "^7.9.0",
- "multi-range-slider-vue": "^1.1.4",
- "ord-schema": "^0.3.54",
- "vue": "^3.4.33",
- "vue-router": "^4.0.3",
- "vuex": "^4.1.0"
+ "ord-schema": "^0.3.99",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-hook-form": "^7.63.0",
+ "react-range": "^1.10.0",
+ "react-router-dom": "^7.9.1",
+ "sass": "^1.93.1",
+ "zod": "^4.1.11"
},
"devDependencies": {
- "@eslint/js": "^9.20.0",
- "@vitejs/plugin-vue": "^5.2.4",
- "eslint": "^9.20.0",
- "eslint-plugin-vue": "^9.32.0",
- "globals": "^15.14.0",
+ "@eslint/js": "^9.36.0",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
+ "eslint": "^9.36.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.4.0",
"prettier": "^3.4.2",
- "pug": "^3.0.2",
- "sass": "^1.32.7",
- "vite": "^6.4.2",
- "vite-plugin-static-copy": "^2.2.0"
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.44.0",
+ "vite": "^7.1.7"
}
},
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
+ "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/parser": {
- "version": "7.29.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
- "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "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.0.0"
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
}
},
- "node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "node_modules/@babel/core/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,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@babel/helper-compilation-targets/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,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
}
},
- "node_modules/@esbuild/android-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/android-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/@babel/parser": {
+ "version": "7.29.3",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.0.0"
}
},
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
- "cpu": [
- "loong64"
- ],
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
- "cpu": [
- "mips64el"
- ],
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
- "cpu": [
- "riscv64"
- ],
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
- "cpu": [
- "s390x"
- ],
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "darwin"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
"engines": {
- "node": ">=18"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
+ "license": "Apache-2.0",
"engines": {
- "node": ">=18"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
"engines": {
- "node": ">=18"
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
- "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
- "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
- "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "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,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
- "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -576,8 +402,6 @@
},
"node_modules/@eslint/config-helpers": {
"version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -589,8 +413,6 @@
},
"node_modules/@eslint/core": {
"version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -602,8 +424,6 @@
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.5",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
- "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -626,8 +446,6 @@
},
"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,
"license": "MIT",
"engines": {
@@ -639,8 +457,6 @@
},
"node_modules/@eslint/js": {
"version": "9.39.4",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
- "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -652,8 +468,6 @@
},
"node_modules/@eslint/object-schema": {
"version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -662,8 +476,6 @@
},
"node_modules/@eslint/plugin-kit": {
"version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -674,10 +486,102 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.26.28",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
+ "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.8",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
+ "node_modules/@headlessui/react": {
+ "version": "2.2.10",
+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz",
+ "integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.26.16",
+ "@react-aria/focus": "^3.20.2",
+ "@react-aria/interactions": "^3.25.0",
+ "@tanstack/react-virtual": "^3.13.9",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz",
+ "integrity": "sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.2",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
- "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -689,8 +593,6 @@
},
"node_modules/@humanfs/node": {
"version": "0.16.8",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
- "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -704,8 +606,6 @@
},
"node_modules/@humanfs/types": {
"version": "0.15.0",
- "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
- "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -714,8 +614,6 @@
},
"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,
"license": "Apache-2.0",
"engines": {
@@ -728,8 +626,6 @@
},
"node_modules/@humanwhocodes/retry": {
"version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -740,55 +636,83 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "license": "MIT"
+ "node_modules/@internationalized/date": {
+ "version": "3.12.1",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz",
+ "integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/number": {
+ "version": "3.6.6",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.6.tgz",
+ "integrity": "sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/string": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.8.tgz",
+ "integrity": "sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
},
- "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==",
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
}
},
- "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==",
+ "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==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">=6.0.0"
}
},
- "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==",
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
- "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -821,18 +745,15 @@
"@parcel/watcher-win32-x64": "2.5.6"
}
},
- "node_modules/@parcel/watcher-android-arm64": {
+ "node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
- "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "android"
+ "darwin"
],
"engines": {
"node": ">= 10.0.0"
@@ -842,10 +763,53 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher-darwin-arm64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
- "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+ "node_modules/@react-aria/focus": {
+ "version": "3.22.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.0.tgz",
+ "integrity": "sha512-ZfDOVuVhqDsM9mkNji3QUZ/d40JhlVgXrDkrfXylM1035QCrcTHN7m2DpbE95sU2A8EQb4wikvt5jM6K/73BPg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0",
+ "react-aria": "3.48.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/interactions": {
+ "version": "3.28.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.0.tgz",
+ "integrity": "sha512-OXwdU1EWFdMxmr/K1CXNGJzmNlCClByb+PuCaqUyzBymHPCGVhawirLIon/CrIN5psh3AiWpHSh4H0WeJdVpng==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-types/shared": "^3.34.0",
+ "@swc/helpers": "^0.5.0",
+ "react-aria": "3.48.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/shared": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.34.0.tgz",
+ "integrity": "sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.3",
"cpu": [
"arm64"
],
@@ -854,734 +818,741 @@
"optional": true,
"os": [
"darwin"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ ]
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
+ "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
}
},
- "node_modules/@parcel/watcher-darwin-x64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
- "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@tanstack/query-core": {
+ "version": "5.100.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.11.tgz",
+ "integrity": "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
}
},
- "node_modules/@parcel/watcher-freebsd-x64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
- "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@tanstack/react-query": {
+ "version": "5.100.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.11.tgz",
+ "integrity": "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg==",
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10.0.0"
+ "dependencies": {
+ "@tanstack/query-core": "5.100.11"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
}
},
- "node_modules/@parcel/watcher-linux-arm-glibc": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
- "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.13.25",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.25.tgz",
+ "integrity": "sha512-bmNoqMu6gcAW9JGrKVB0Q1tN1i5RONZF8r1fW0bbE4Oyf3DwEGnzzQJ2OW+Ozg1P4s8PyugkHg2ULZoFQN+cqw==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10.0.0"
+ "dependencies": {
+ "@tanstack/virtual-core": "3.15.0"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/@parcel/watcher-linux-arm-musl": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
- "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.15.0.tgz",
+ "integrity": "sha512-0AwPGx0I8QxPYjAxShT/+z+ZOe9u8mW5rsXvivCTjRfRmz9a43+3mRyi4wwlyoUqOC56q/jatKa0Bh9M99BEHQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
}
},
- "node_modules/@parcel/watcher-linux-arm64-glibc": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
- "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
- "cpu": [
- "arm64"
- ],
+ "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,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
}
},
- "node_modules/@parcel/watcher-linux-arm64-musl": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
- "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@babel/types": "^7.0.0"
}
},
- "node_modules/@parcel/watcher-linux-x64-glibc": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
- "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
- "cpu": [
- "x64"
- ],
+ "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,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
}
},
- "node_modules/@parcel/watcher-linux-x64-musl": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
- "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@babel/types": "^7.28.2"
}
},
- "node_modules/@parcel/watcher-win32-arm64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
- "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
}
},
- "node_modules/@parcel/watcher-win32-ia32": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
- "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@types/d3-selection": "*"
}
},
- "node_modules/@parcel/watcher-win32-x64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
- "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "dependencies": {
+ "@types/d3-selection": "*"
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz",
- "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz",
- "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz",
- "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz",
- "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz",
- "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz",
- "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz",
- "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz",
- "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz",
- "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz",
- "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz",
- "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/geojson": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz",
- "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz",
- "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz",
- "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz",
- "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz",
- "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz",
- "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==",
- "cpu": [
- "s390x"
- ],
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz",
- "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.15",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
+ "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz",
- "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
},
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz",
- "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@types/react-router": {
+ "version": "5.1.20",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
+ "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*"
+ }
},
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz",
- "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@types/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
"license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz",
- "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz",
+ "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/type-utils": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.59.4",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
},
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz",
- "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "engines": {
+ "node": ">= 4"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz",
- "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz",
+ "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz",
- "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz",
+ "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.59.4",
+ "@typescript-eslint/types": "^8.59.4",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
},
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz",
+ "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
},
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz",
+ "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
},
- "node_modules/@vitejs/plugin-vue": {
- "version": "5.2.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
- "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz",
+ "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
+ },
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "vite": "^5.0.0 || ^6.0.0",
- "vue": "^3.2.25"
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@vue/compiler-core": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz",
- "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==",
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz",
+ "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.3",
- "@vue/shared": "3.5.34",
- "entities": "^7.0.1",
- "estree-walker": "^2.0.2",
- "source-map-js": "^1.2.1"
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@vue/compiler-dom": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz",
- "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==",
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz",
+ "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.34",
- "@vue/shared": "3.5.34"
+ "@typescript-eslint/project-service": "8.59.4",
+ "@typescript-eslint/tsconfig-utils": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@vue/compiler-sfc": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz",
- "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==",
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.3",
- "@vue/compiler-core": "3.5.34",
- "@vue/compiler-dom": "3.5.34",
- "@vue/compiler-ssr": "3.5.34",
- "@vue/shared": "3.5.34",
- "estree-walker": "^2.0.2",
- "magic-string": "^0.30.21",
- "postcss": "^8.5.14",
- "source-map-js": "^1.2.1"
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
- "node_modules/@vue/compiler-ssr": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz",
- "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==",
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.34",
- "@vue/shared": "3.5.34"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
- "node_modules/@vue/devtools-api": {
- "version": "6.6.4",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
- "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
- "license": "MIT"
- },
- "node_modules/@vue/reactivity": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz",
- "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==",
- "license": "MIT",
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "@vue/shared": "3.5.34"
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/@vue/runtime-core": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz",
- "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==",
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz",
+ "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.34",
- "@vue/shared": "3.5.34"
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@vue/runtime-dom": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz",
- "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==",
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz",
+ "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.34",
- "@vue/runtime-core": "3.5.34",
- "@vue/shared": "3.5.34",
- "csstype": "^3.2.3"
+ "@typescript-eslint/types": "8.59.4",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/@vue/server-renderer": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz",
- "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==",
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.34",
- "@vue/shared": "3.5.34"
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
- "vue": "3.5.34"
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/@vue/shared": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz",
- "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==",
- "license": "MIT"
- },
"node_modules/acorn": {
"version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -1593,18 +1564,26 @@
},
"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,
"license": "MIT",
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/ajv": {
"version": "6.15.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
- "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1619,133 +1598,74 @@
}
},
"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,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/anymatch/node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "version": "4.3.0",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
"engines": {
- "node": ">=8.6"
+ "node": ">=8"
},
"funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"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,
"license": "Python-2.0"
},
- "node_modules/asap": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
- "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
- "dev": true,
- "license": "MIT"
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
},
- "node_modules/assert-never": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz",
- "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==",
- "dev": true,
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
- "node_modules/babel-walk": {
- "version": "3.0.0-canary-5",
- "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz",
- "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==",
- "dev": true,
+ "node_modules/axios": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
+ "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.9.6"
- },
- "engines": {
- "node": ">= 10.0.0"
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
}
},
"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,
"license": "MIT"
},
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
+ "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">=6.0.0"
}
},
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/brace-expansion": {
"version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1753,48 +1673,42 @@
"concat-map": "0.0.1"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
- "type": "github",
- "url": "https://github.com/sponsors/feross"
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
},
{
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
- "type": "consulting",
- "url": "https://feross.org/support"
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -1804,37 +1718,37 @@
"node": ">= 0.4"
}
},
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "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"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
"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,
"license": "MIT",
"dependencies": {
@@ -1848,21 +1762,8 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/character-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
- "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-regex": "^1.0.3"
- }
- },
"node_modules/chokidar": {
"version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
@@ -1874,10 +1775,17 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"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,
"license": "MIT",
"dependencies": {
@@ -1889,15 +1797,23 @@
},
"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,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"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==",
"license": "MIT",
"engines": {
"node": ">= 10"
@@ -1905,26 +1821,31 @@
},
"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,
"license": "MIT"
},
- "node_modules/constantinople": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
- "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==",
+ "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,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.6.0",
- "@babel/types": "^7.6.1"
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1936,29 +1857,12 @@
"node": ">= 8"
}
},
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/csstype": {
"version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"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==",
"license": "ISC",
"dependencies": {
"d3-array": "3",
@@ -1998,8 +1902,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
@@ -2010,8 +1912,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2019,8 +1919,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
@@ -2035,8 +1933,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-path": "1 - 3"
@@ -2047,8 +1943,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2056,8 +1950,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-array": "^3.2.0"
@@ -2068,8 +1960,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"delaunator": "5"
@@ -2080,8 +1970,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2089,8 +1977,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
@@ -2102,8 +1988,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"commander": "7",
@@ -2127,8 +2011,6 @@
},
"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==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
@@ -2136,8 +2018,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-dsv": "1 - 3"
@@ -2148,8 +2028,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
@@ -2162,8 +2040,6 @@
},
"node_modules/d3-format": {
"version": "3.1.2",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
- "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2171,8 +2047,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-array": "2.5.0 - 3"
@@ -2183,8 +2057,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2192,8 +2064,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
@@ -2204,8 +2074,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2213,8 +2081,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2222,8 +2088,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2231,8 +2095,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2240,8 +2102,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
@@ -2256,8 +2116,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
@@ -2269,8 +2127,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2278,8 +2134,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
@@ -2290,8 +2144,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
@@ -2302,8 +2154,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
@@ -2314,8 +2164,6 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -2323,8 +2171,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
@@ -2342,8 +2188,6 @@
},
"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==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
@@ -2358,9 +2202,6 @@
},
"node_modules/debug": {
"version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -2376,43 +2217,35 @@
},
"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,
"license": "MIT"
},
"node_modules/delaunator": {
"version": "5.1.0",
- "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
- "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
"license": "ISC",
"dependencies": {
"robust-predicates": "^3.0.2"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
},
- "node_modules/doctypes": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
- "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/dunder-proto": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -2423,23 +2256,15 @@
"node": ">= 0.4"
}
},
- "node_modules/entities": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
- "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.360",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz",
+ "integrity": "sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/es-define-property": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2447,9 +2272,6 @@
},
"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,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2457,9 +2279,6 @@
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -2468,10 +2287,25 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -2482,38 +2316,46 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.12",
- "@esbuild/android-arm": "0.25.12",
- "@esbuild/android-arm64": "0.25.12",
- "@esbuild/android-x64": "0.25.12",
- "@esbuild/darwin-arm64": "0.25.12",
- "@esbuild/darwin-x64": "0.25.12",
- "@esbuild/freebsd-arm64": "0.25.12",
- "@esbuild/freebsd-x64": "0.25.12",
- "@esbuild/linux-arm": "0.25.12",
- "@esbuild/linux-arm64": "0.25.12",
- "@esbuild/linux-ia32": "0.25.12",
- "@esbuild/linux-loong64": "0.25.12",
- "@esbuild/linux-mips64el": "0.25.12",
- "@esbuild/linux-ppc64": "0.25.12",
- "@esbuild/linux-riscv64": "0.25.12",
- "@esbuild/linux-s390x": "0.25.12",
- "@esbuild/linux-x64": "0.25.12",
- "@esbuild/netbsd-arm64": "0.25.12",
- "@esbuild/netbsd-x64": "0.25.12",
- "@esbuild/openbsd-arm64": "0.25.12",
- "@esbuild/openbsd-x64": "0.25.12",
- "@esbuild/openharmony-arm64": "0.25.12",
- "@esbuild/sunos-x64": "0.25.12",
- "@esbuild/win32-arm64": "0.25.12",
- "@esbuild/win32-ia32": "0.25.12",
- "@esbuild/win32-x64": "0.25.12"
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
"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,
"license": "MIT",
"engines": {
@@ -2525,8 +2367,6 @@
},
"node_modules/eslint": {
"version": "9.39.4",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
- "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2583,49 +2423,31 @@
}
}
},
- "node_modules/eslint-plugin-vue": {
- "version": "9.33.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz",
- "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==",
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "globals": "^13.24.0",
- "natural-compare": "^1.4.0",
- "nth-check": "^2.1.1",
- "postcss-selector-parser": "^6.0.15",
- "semver": "^7.6.3",
- "vue-eslint-parser": "^9.4.3",
- "xml-name-validator": "^4.0.0"
- },
"engines": {
- "node": "^14.17.0 || >=16.0.0"
+ "node": ">=10"
},
"peerDependencies": {
- "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
+ "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-vue/node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "type-fest": "^0.20.2"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "peerDependencies": {
+ "eslint": ">=8.40"
}
},
"node_modules/eslint-scope": {
"version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2641,8 +2463,6 @@
},
"node_modules/eslint-visitor-keys": {
"version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -2654,8 +2474,6 @@
},
"node_modules/espree": {
"version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2672,8 +2490,6 @@
},
"node_modules/esquery": {
"version": "1.7.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
- "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -2685,8 +2501,6 @@
},
"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,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2698,24 +2512,14 @@
},
"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,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
- "node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "license": "MIT"
- },
"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,
"license": "BSD-2-Clause",
"engines": {
@@ -2724,69 +2528,21 @@
},
"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,
"license": "MIT"
},
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"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,
"license": "MIT"
},
"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,
"license": "MIT"
},
- "node_modules/fastq": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
- "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2803,8 +2559,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -2814,23 +2568,8 @@
"node": ">=16.0.0"
}
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"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,
"license": "MIT",
"dependencies": {
@@ -2846,8 +2585,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -2860,32 +2597,48 @@
},
"node_modules/flatted": {
"version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
- "node_modules/fs-extra": {
- "version": "11.3.5",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
- "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
- "dev": true,
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
},
"engines": {
- "node": ">=14.14"
+ "node": ">= 6"
}
},
"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,
"license": "MIT",
"optional": true,
"os": [
@@ -2897,19 +2650,23 @@
},
"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==",
- "dev": true,
"license": "MIT",
"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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -2932,9 +2689,6 @@
},
"node_modules/get-proto": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -2946,8 +2700,6 @@
},
"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,
"license": "ISC",
"dependencies": {
@@ -2958,9 +2710,9 @@
}
},
"node_modules/globals": {
- "version": "15.15.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
- "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2972,15 +2724,10 @@
},
"node_modules/google-protobuf": {
"version": "3.19.4",
- "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.4.tgz",
- "integrity": "sha512-OIPNCxsG2lkIvf+P5FNfJ/Km95CsXOBecS9ZcAU6m2Rq3svc0Apl9nB3GMDNKfQ9asNv4KjyAqGwPQFrVle3Yg==",
"license": "BSD-3-Clause"
},
"node_modules/gopd": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2989,17 +2736,8 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
"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,
"license": "MIT",
"engines": {
@@ -3008,9 +2746,6 @@
},
"node_modules/has-symbols": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3021,9 +2756,6 @@
},
"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,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -3037,21 +2769,29 @@
},
"node_modules/hasown": {
"version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
- "function-bind": "^1.1.2"
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">= 6"
}
},
"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==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -3060,30 +2800,8 @@
"node": ">=0.10.0"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
"node_modules/ignore": {
"version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3092,15 +2810,10 @@
},
"node_modules/immutable": {
"version": "5.1.5",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
- "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
- "dev": true,
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3116,8 +2829,6 @@
},
"node_modules/imurmurhash": {
"version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3126,71 +2837,14 @@
},
"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==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.16.2",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
- "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-expression": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz",
- "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "acorn": "^7.1.1",
- "object-assign": "^4.1.1"
- }
- },
- "node_modules/is-expression/node_modules/acorn": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
- "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
"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,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -3198,9 +2852,7 @@
},
"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,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -3209,60 +2861,20 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-promise": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
- "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-regex": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
- "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/isexe": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
- "node_modules/js-stringify": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
- "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==",
+ "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==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3272,55 +2884,49 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"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,
"license": "MIT"
},
"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,
"license": "MIT"
},
"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,
"license": "MIT"
},
- "node_modules/jsonfile": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
- "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "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,
"license": "MIT",
- "dependencies": {
- "universalify": "^2.0.0"
+ "bin": {
+ "json5": "lib/cli.js"
},
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/jstransformer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
- "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-promise": "^2.0.0",
- "promise": "^7.0.1"
+ "engines": {
+ "node": ">=6"
}
},
"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,
"license": "MIT",
"dependencies": {
@@ -3329,8 +2935,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -3343,8 +2947,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -3357,80 +2959,51 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/lodash": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
- "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
- "dev": true,
- "license": "MIT"
- },
"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,
"license": "MIT"
},
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "license": "MIT",
+ "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,
+ "license": "ISC",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "yallist": "^3.0.2"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">= 0.6"
}
},
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
+ "mime-db": "1.52.0"
},
"engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/micromatch/node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -3442,21 +3015,11 @@
},
"node_modules/ms": {
"version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
- "node_modules/multi-range-slider-vue": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/multi-range-slider-vue/-/multi-range-slider-vue-1.1.4.tgz",
- "integrity": "sha512-QnYl/59+kWTer5aMY039CigSVbSiIy5gDBnAX/0O0IZIY5az9nWL7tJv5dP77fzeDxiWsTWzsVUW8fGDcC8mEg==",
- "license": "ISC"
- },
"node_modules/nanoid": {
"version": "3.3.12",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
- "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -3473,56 +3036,26 @@
},
"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,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "7.1.1",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
- "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
- "dev": true,
"license": "MIT",
"optional": true
},
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
- }
- },
- "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==",
+ "node_modules/node-releases": {
+ "version": "2.0.45",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.45.tgz",
+ "integrity": "sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=18"
}
},
"node_modules/optionator": {
"version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3539,8 +3072,6 @@
},
"node_modules/ord-schema": {
"version": "0.3.99",
- "resolved": "https://registry.npmjs.org/ord-schema/-/ord-schema-0.3.99.tgz",
- "integrity": "sha512-GXvGEL81HHrCv+YzKGUX87VukjNSlPCSTToAOC5a+lgAuhWjrjoyse/5Kae4/awSr0tDVn/BayzB6UrUCLAPyg==",
"license": "Apache-2.0",
"dependencies": {
"google-protobuf": "<3.20"
@@ -3548,8 +3079,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -3564,8 +3093,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -3578,23 +3105,8 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/p-map": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
- "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "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==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3606,8 +3118,6 @@
},
"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,
"license": "MIT",
"engines": {
@@ -3616,32 +3126,20 @@
},
"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,
"license": "MIT",
"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==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/picocolors": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -3652,8 +3150,7 @@
},
"node_modules/postcss": {
"version": "8.5.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3678,24 +3175,8 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
"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,
"license": "MIT",
"engines": {
@@ -3704,8 +3185,6 @@
},
"node_modules/prettier": {
"version": "3.8.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
- "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3718,250 +3197,181 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/promise": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
- "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "asap": "~2.0.3"
- }
- },
- "node_modules/pug": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz",
- "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==",
- "dev": true,
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
- "dependencies": {
- "pug-code-gen": "^3.0.4",
- "pug-filters": "^4.0.0",
- "pug-lexer": "^5.0.1",
- "pug-linker": "^4.0.0",
- "pug-load": "^3.0.0",
- "pug-parser": "^6.0.0",
- "pug-runtime": "^3.0.1",
- "pug-strip-comments": "^2.0.0"
+ "engines": {
+ "node": ">=10"
}
},
- "node_modules/pug-attrs": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz",
- "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==",
+ "node_modules/punycode": {
+ "version": "2.3.1",
"dev": true,
"license": "MIT",
- "dependencies": {
- "constantinople": "^4.0.1",
- "js-stringify": "^1.0.2",
- "pug-runtime": "^3.0.0"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/pug-code-gen": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz",
- "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==",
- "dev": true,
+ "node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
"license": "MIT",
- "dependencies": {
- "constantinople": "^4.0.1",
- "doctypes": "^1.1.0",
- "js-stringify": "^1.0.2",
- "pug-attrs": "^3.0.0",
- "pug-error": "^2.1.0",
- "pug-runtime": "^3.0.1",
- "void-elements": "^3.1.0",
- "with": "^7.0.0"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/pug-error": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz",
- "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pug-filters": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz",
- "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==",
- "dev": true,
- "license": "MIT",
+ "node_modules/react-aria": {
+ "version": "3.48.0",
+ "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.48.0.tgz",
+ "integrity": "sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==",
+ "license": "Apache-2.0",
"dependencies": {
- "constantinople": "^4.0.1",
- "jstransformer": "1.0.0",
- "pug-error": "^2.0.0",
- "pug-walk": "^2.0.0",
- "resolve": "^1.15.1"
+ "@internationalized/date": "^3.12.1",
+ "@internationalized/number": "^3.6.6",
+ "@internationalized/string": "^3.2.8",
+ "@react-types/shared": "^3.34.0",
+ "@swc/helpers": "^0.5.0",
+ "aria-hidden": "^1.2.3",
+ "clsx": "^2.0.0",
+ "react-stately": "3.46.0",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
- "node_modules/pug-lexer": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz",
- "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==",
- "dev": true,
+ "node_modules/react-dom": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
+ "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
"license": "MIT",
"dependencies": {
- "character-parser": "^2.2.0",
- "is-expression": "^4.0.0",
- "pug-error": "^2.0.0"
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.6"
}
},
- "node_modules/pug-linker": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz",
- "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==",
- "dev": true,
+ "node_modules/react-hook-form": {
+ "version": "7.76.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.76.0.tgz",
+ "integrity": "sha512-eKtLGgFeSgkHqQD8J59AMZ9a4uD1D83iSIzt4YlTGD7liDen5rrjcUO1rVIGd9yC1gofryjtHbv+4ny4hkLWlw==",
"license": "MIT",
- "dependencies": {
- "pug-error": "^2.0.0",
- "pug-walk": "^2.0.0"
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
- "node_modules/pug-load": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz",
- "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==",
- "dev": true,
+ "node_modules/react-range": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/react-range/-/react-range-1.10.0.tgz",
+ "integrity": "sha512-kDo0LiBUHIQIP8menx0UoxTnHr7UXBYpIYl/DR9jCaO1o29VwvCLpkP/qOTNQz5hkJadPg1uEM07XJcJ1XGoKw==",
"license": "MIT",
- "dependencies": {
- "object-assign": "^4.1.1",
- "pug-walk": "^2.0.0"
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
}
},
- "node_modules/pug-parser": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz",
- "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==",
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "pug-error": "^2.0.0",
- "token-stream": "1.0.0"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/pug-runtime": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz",
- "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pug-strip-comments": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz",
- "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==",
- "dev": true,
+ "node_modules/react-router": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
+ "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
"license": "MIT",
"dependencies": {
- "pug-error": "^2.0.0"
- }
- },
- "node_modules/pug-walk": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz",
- "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "MIT",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
"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": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
}
- ],
- "license": "MIT"
+ }
},
- "node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
+ "node_modules/react-router-dom": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz",
+ "integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==",
"license": "MIT",
+ "dependencies": {
+ "react-router": "7.15.1"
+ },
"engines": {
- "node": ">= 14.18.0"
+ "node": ">=20.0.0"
},
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
}
},
- "node_modules/resolve": {
- "version": "1.22.12",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
- "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
- "dev": true,
- "license": "MIT",
+ "node_modules/react-stately": {
+ "version": "3.46.0",
+ "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.46.0.tgz",
+ "integrity": "sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==",
+ "license": "Apache-2.0",
"dependencies": {
- "es-errors": "^1.3.0",
- "is-core-module": "^2.16.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
+ "@internationalized/date": "^3.12.1",
+ "@internationalized/number": "^3.6.6",
+ "@internationalized/string": "^3.2.8",
+ "@react-types/shared": "^3.34.0",
+ "@swc/helpers": "^0.5.0",
+ "use-sync-external-store": "^1.6.0"
},
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">= 14.18.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
"node_modules/robust-predicates": {
"version": "3.0.3",
- "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
- "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.60.3",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz",
- "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4005,52 +3415,19 @@
},
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
- "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"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "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==",
"license": "BSD-3-Clause"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sass": {
"version": "1.99.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz",
- "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
@@ -4067,10 +3444,14 @@
"@parcel/watcher": "^2.4.1"
}
},
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "7.8.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
- "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -4080,10 +3461,14 @@
"node": ">=10"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"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,
"license": "MIT",
"dependencies": {
@@ -4095,8 +3480,6 @@
},
"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,
"license": "MIT",
"engines": {
@@ -4105,8 +3488,6 @@
},
"node_modules/source-map-js": {
"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==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -4114,8 +3495,6 @@
},
"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,
"license": "MIT",
"engines": {
@@ -4127,8 +3506,6 @@
},
"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,
"license": "MIT",
"dependencies": {
@@ -4138,23 +3515,14 @@
"node": ">=8"
}
},
- "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==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "node_modules/tabbable": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
+ "license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.16",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
- "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4168,30 +3536,27 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
"engines": {
- "node": ">=8.0"
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
}
},
- "node_modules/token-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
- "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==",
- "dev": true,
- "license": "MIT"
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
},
"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,
"license": "MIT",
"dependencies": {
@@ -4201,65 +3566,111 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz",
+ "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==",
"dev": true,
- "license": "(MIT OR CC0-1.0)",
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.59.4",
+ "@typescript-eslint/parser": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4"
+ },
"engines": {
- "node": ">=10"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"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"
+ }
+ ],
"license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.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,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
},
"node_modules/vite": {
- "version": "6.4.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
- "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
+ "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esbuild": "^0.25.0",
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2",
- "postcss": "^8.5.3",
- "rollup": "^4.34.9",
- "tinyglobby": "^0.2.13"
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -4268,14 +3679,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
- "less": "*",
+ "less": "^4.0.0",
"lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -4316,225 +3727,8 @@
}
}
},
- "node_modules/vite-plugin-static-copy": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.3.2.tgz",
- "integrity": "sha512-iwrrf+JupY4b9stBttRWzGHzZbeMjAHBhkrn67MNACXJVjEMRpCI10Q3AkxdBkl45IHaTfw/CNVevzQhP7yTwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.5.3",
- "fast-glob": "^3.2.11",
- "fs-extra": "^11.1.0",
- "p-map": "^7.0.3",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "peerDependencies": {
- "vite": "^5.0.0 || ^6.0.0"
- }
- },
- "node_modules/vite-plugin-static-copy/node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/vite-plugin-static-copy/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/vite-plugin-static-copy/node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/vite-plugin-static-copy/node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/void-elements": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
- "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/vue": {
- "version": "3.5.34",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
- "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
- "license": "MIT",
- "dependencies": {
- "@vue/compiler-dom": "3.5.34",
- "@vue/compiler-sfc": "3.5.34",
- "@vue/runtime-dom": "3.5.34",
- "@vue/server-renderer": "3.5.34",
- "@vue/shared": "3.5.34"
- },
- "peerDependencies": {
- "typescript": "*"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/vue-eslint-parser": {
- "version": "9.4.3",
- "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
- "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.4",
- "eslint-scope": "^7.1.1",
- "eslint-visitor-keys": "^3.3.0",
- "espree": "^9.3.1",
- "esquery": "^1.4.0",
- "lodash": "^4.17.21",
- "semver": "^7.3.6"
- },
- "engines": {
- "node": "^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- },
- "peerDependencies": {
- "eslint": ">=6.0.0"
- }
- },
- "node_modules/vue-eslint-parser/node_modules/eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/vue-eslint-parser/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,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/vue-eslint-parser/node_modules/espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.9.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/vue-router": {
- "version": "4.6.4",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
- "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
- "license": "MIT",
- "dependencies": {
- "@vue/devtools-api": "^6.6.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/posva"
- },
- "peerDependencies": {
- "vue": "^3.5.0"
- }
- },
- "node_modules/vuex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
- "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
- "license": "MIT",
- "dependencies": {
- "@vue/devtools-api": "^6.0.0-beta.11"
- },
- "peerDependencies": {
- "vue": "^3.2.0"
- }
- },
"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,
"license": "ISC",
"dependencies": {
@@ -4547,46 +3741,23 @@
"node": ">= 8"
}
},
- "node_modules/with": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz",
- "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.9.6",
- "@babel/types": "^7.9.6",
- "assert-never": "^1.2.1",
- "babel-walk": "3.0.0-canary-5"
- },
- "engines": {
- "node": ">= 10.0.0"
- }
- },
"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,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/xml-name-validator": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
- "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12"
- }
+ "license": "ISC"
},
"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,
"license": "MIT",
"engines": {
@@ -4595,6 +3766,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/app/package.json b/app/package.json
index 4c8e6387..d157c884 100644
--- a/app/package.json
+++ b/app/package.json
@@ -1,36 +1,47 @@
{
"name": "app",
- "version": "0.1.0",
"private": true,
+ "version": "0.0.0",
"type": "module",
"scripts": {
- "serve": "vite",
"dev": "vite",
- "build": "vite build",
+ "serve": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
"preview": "vite preview",
- "lint": "eslint src",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
- "buffer": "^6.0.3",
+ "@headlessui/react": "^2.2.8",
+ "@heroicons/react": "^2.2.0",
+ "@hookform/resolvers": "^5.2.2",
+ "@tanstack/react-query": "^5.90.2",
+ "@types/d3": "^7.4.3",
+ "@types/react-router-dom": "^5.3.3",
+ "axios": "^1.12.2",
"d3": "^7.9.0",
- "multi-range-slider-vue": "^1.1.4",
- "ord-schema": "^0.3.54",
- "vue": "^3.4.33",
- "vue-router": "^4.0.3",
- "vuex": "^4.1.0"
+ "ord-schema": "^0.3.99",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-hook-form": "^7.63.0",
+ "react-range": "^1.10.0",
+ "react-router-dom": "^7.9.1",
+ "sass": "^1.93.1",
+ "zod": "^4.1.11"
},
"devDependencies": {
- "@eslint/js": "^9.20.0",
- "@vitejs/plugin-vue": "^5.2.4",
- "eslint": "^9.20.0",
- "eslint-plugin-vue": "^9.32.0",
- "globals": "^15.14.0",
+ "@eslint/js": "^9.36.0",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
+ "eslint": "^9.36.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.4.0",
"prettier": "^3.4.2",
- "pug": "^3.0.2",
- "sass": "^1.32.7",
- "vite": "^6.4.2",
- "vite-plugin-static-copy": "^2.2.0"
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.44.0",
+ "vite": "^7.1.7"
}
}
diff --git a/app/public/favicon.ico b/app/public/favicon.ico
index df36fcfb..01b34b25 100644
Binary files a/app/public/favicon.ico and b/app/public/favicon.ico differ
diff --git a/app/public/vite.svg b/app/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/app/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/App.scss b/app/src/App.scss
new file mode 100644
index 00000000..d55b4002
--- /dev/null
+++ b/app/src/App.scss
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use './styles/vars.scss' as vars;
+@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
+
+body {
+ font-family: 'Roboto', sans-serif;
+ margin: 0;
+}
+
+#root,
+#main-container {
+ min-height: 100vh;
+ min-width: 100vw;
+}
+
+#main-container {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ background-color: vars.$lightgrey;
+ min-width: 800px;
+
+ &.full-height {
+ grid-template-rows: 1fr;
+ }
+}
+
+button {
+ background-color: vars.$linkblue;
+ border: none;
+ color: white;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ padding: 0.25rem 0.5rem;
+}
+
+/* Multi-value-slider styles */
+.multi-range-slider {
+ border: none !important;
+ box-shadow: none !important;
+
+ .thumb::before {
+ height: 18px !important;
+ width: 18px !important;
+ margin: -5px !important;
+ box-shadow: none !important;
+ background-color: vars.$linkblue !important;
+ }
+
+ .bar-left,
+ .bar-inner,
+ .bar-right {
+ box-shadow: none !important;
+ border: grey 1px solid;
+ }
+
+ .bar-inner {
+ background-color: vars.$linkblue !important;
+ }
+}
diff --git a/app/src/App.tsx b/app/src/App.tsx
new file mode 100644
index 00000000..0475ecc7
--- /dev/null
+++ b/app/src/App.tsx
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import HeaderNav from './components/HeaderNav';
+import MainFooter from './components/MainFooter';
+import Home from './views/Home';
+import About from './views/About';
+import MainBrowse from './views/browse/MainBrowse';
+import MainSelectedSet from './views/browse/selected-set/MainSelectedSet';
+import MainSearch from './views/search/MainSearch';
+import MainDatasetView from './views/dataset-view/MainDatasetView';
+import MainReactionView from './views/reaction-view/MainReactionView';
+import './App.scss';
+
+const queryClient = new QueryClient();
+
+const AppContent: React.FC = () => {
+ return (
+
+
+
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+
+
+ );
+};
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/app/src/App.vue b/app/src/App.vue
deleted file mode 100644
index 4b4fc319..00000000
--- a/app/src/App.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-#main-container(
- :class='noHeaderFooter ? "full-height" : ""'
-)
- HeaderNav(v-if='!noHeaderFooter')
- router-view
- MainFooter(v-if='!noHeaderFooter')
-
-
-
diff --git a/app/src/assets/react.svg b/app/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/app/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/components/CopyButton.scss b/app/src/components/CopyButton.scss
new file mode 100644
index 00000000..a022850c
--- /dev/null
+++ b/app/src/components/CopyButton.scss
@@ -0,0 +1,61 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars.scss' as vars;
+
+.copy-button-main {
+ button {
+ padding: 0.25rem 0.5rem;
+ display: flex;
+ column-gap: 0.25rem;
+ background-color: vars.$linkblue;
+ color: white;
+ border: none;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ align-items: center;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: vars.$hoverblue;
+ }
+
+ i {
+ font-size: 1rem;
+ }
+ }
+
+ #copy-notification {
+ position: fixed;
+ background-color: vars.$darkgrey;
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ z-index: 1000;
+ transform: translate(-50%, -100%);
+ margin-top: -0.5rem;
+ opacity: 1;
+ transition: opacity 0.25s ease;
+
+ &.fade-enter {
+ opacity: 0;
+ }
+
+ &.fade-enter-active {
+ opacity: 1;
+ }
+ }
+}
diff --git a/app/src/components/CopyButton.tsx b/app/src/components/CopyButton.tsx
new file mode 100644
index 00000000..d32d64a0
--- /dev/null
+++ b/app/src/components/CopyButton.tsx
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import './CopyButton.scss';
+
+interface CopyButtonProps {
+ textToCopy: string;
+ icon?: string;
+ buttonText?: string;
+}
+
+const CopyButton: React.FC = ({ textToCopy, icon = 'content_copy', buttonText = '' }) => {
+ const [displayNotification, setDisplayNotification] = useState(false);
+ const [notificationStyle, setNotificationStyle] = useState({
+ top: 0,
+ left: 0,
+ });
+
+ const handleCopy = async (event: React.MouseEvent) => {
+ // Move notification block to mouse cursor position
+ const { clientX, clientY } = event;
+ setNotificationStyle({
+ top: clientY,
+ left: clientX,
+ });
+
+ try {
+ await navigator.clipboard.writeText(textToCopy);
+ setDisplayNotification(true);
+
+ setTimeout(() => {
+ setDisplayNotification(false);
+ }, 1500);
+ } catch (err) {
+ console.error('Failed to copy text:', err);
+ }
+ };
+
+ return (
+
+
+ {icon}
+ {buttonText && {buttonText}
}
+
+
+ {displayNotification && (
+
+ Copied to clipboard!
+
+ )}
+
+ );
+};
+
+export default CopyButton;
diff --git a/app/src/components/CopyButton.vue b/app/src/components/CopyButton.vue
deleted file mode 100644
index 34abac95..00000000
--- a/app/src/components/CopyButton.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-.copy-button-main
- button(@click='copy')
- i.material-icons {{icon}}
- .copy(v-if='buttonText') {{buttonText}}
- transition(
- name='fade'
- )
- #copy-notification(
- v-if='displayNotification'
- :style='notificationStyle'
- ) Copied to clipboard!
-
-
-
diff --git a/app/src/components/DownloadResults.scss b/app/src/components/DownloadResults.scss
new file mode 100644
index 00000000..0785a9ac
--- /dev/null
+++ b/app/src/components/DownloadResults.scss
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars.scss' as vars;
+
+.download-results-main {
+ .download-body {
+ height: 100%;
+ display: grid;
+ grid-template-rows: 50% auto 1fr;
+
+ * {
+ margin: auto;
+ width: fit-content;
+ }
+
+ .title {
+ font-size: 1.5rem;
+ }
+
+ .options {
+ label {
+ margin-right: 0.5rem;
+ }
+
+ select {
+ min-width: 7rem;
+ }
+ }
+
+ .download {
+ margin-top: 2rem;
+ }
+ }
+}
diff --git a/app/src/components/DownloadResults.tsx b/app/src/components/DownloadResults.tsx
new file mode 100644
index 00000000..e842a558
--- /dev/null
+++ b/app/src/components/DownloadResults.tsx
@@ -0,0 +1,109 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import FloatingModal from './FloatingModal';
+import './DownloadResults.scss';
+
+interface DownloadResultsProps {
+ reactionIds: string[];
+ showDownloadResults: boolean;
+ onHideDownloadResults: () => void;
+}
+
+const DownloadResults: React.FC = ({
+ reactionIds,
+ showDownloadResults,
+ onHideDownloadResults,
+}) => {
+ const [fileType, setFileType] = useState('pb.gz');
+
+ useEffect(() => {
+ const storedFileType = localStorage.getItem('downloadFileType') || 'pb.gz';
+ setFileType(storedFileType);
+ }, []);
+
+ const handleFileTypeChange = (newFileType: string) => {
+ setFileType(newFileType);
+ // Store selected file type in localStorage
+ localStorage.setItem('downloadFileType', newFileType);
+ };
+
+ const downloadResults = () => {
+ // Create .pb download of search results
+ const xhr = new XMLHttpRequest();
+ xhr.open('POST', '/api/download_search_results');
+ xhr.responseType = 'blob';
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ const url = URL.createObjectURL(xhr.response);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'ord_search_results.pb.gz';
+ link.click();
+ // https://stackoverflow.com/a/56547307.
+ setTimeout(() => {
+ URL.revokeObjectURL(url);
+ link.remove();
+ }, 100);
+ }
+ };
+ xhr.setRequestHeader('Content-Type', 'application/json');
+ xhr.send(JSON.stringify({ reaction_ids: reactionIds }));
+ };
+
+ if (!showDownloadResults) return null;
+
+ return (
+
+
+
+
Select your desired file type and then click download.
+
+ File type:
+ handleFileTypeChange(e.target.value)}
+ >
+ pb.gz
+
+ csv (coming soon)
+
+
+ pbtxt (coming soon)
+
+
+
+
+ Download {fileType} file
+
+
+
+
+ );
+};
+
+export default DownloadResults;
diff --git a/app/src/components/DownloadResults.vue b/app/src/components/DownloadResults.vue
deleted file mode 100644
index 6be2a736..00000000
--- a/app/src/components/DownloadResults.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
-.download-results-main
- floating-modal(
- v-if='showDownloadResults'
- title="Download Results"
- @closeModal='$emit("hideDownloadResults")'
- )
- .download-body
- .title Select your desired file type and then click download.
- .options
- label(for='file-type-select') File type:
- select#file-type-select(v-model='fileType')
- option(value="pb.gz") pb.gz
- option(value="csv" disabled) csv (coming soon)
- option(value="pbtxt" disabled) pbtxt (coming soon)
- .download
- button(@click='downloadResults') Download {{fileType}} file
-
-
-
diff --git a/app/src/components/EntityTable.scss b/app/src/components/EntityTable.scss
new file mode 100644
index 00000000..9ebeea29
--- /dev/null
+++ b/app/src/components/EntityTable.scss
@@ -0,0 +1,145 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars.scss' as vars;
+
+.table-main {
+ width: 100%;
+
+ .table-container {
+ width: 100%;
+
+ .header {
+ .title-holder {
+ .title {
+ font-size: 2rem;
+ font-weight: 700;
+ margin-bottom: 1rem;
+ }
+ }
+ }
+
+ .search-area {
+ padding: 1rem;
+ margin: 0 5%;
+
+ input {
+ width: 30%;
+ min-width: 300px;
+ padding: 0.5rem;
+ border: 1px solid vars.$medgrey;
+ border-radius: 0.25rem;
+ margin-left: 0.5rem;
+ }
+ }
+
+ .content {
+ width: 100%;
+ height: fit-content;
+
+ .entities-holder {
+ height: 100%;
+ display: block;
+ width: 100%;
+
+ > * {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+
+ .entity.no-interact {
+ display: grid;
+ cursor: default;
+ grid-template-columns: 1fr;
+ column-gap: 1rem;
+ align-items: center;
+ padding: 1rem;
+
+ &:hover {
+ background-color: transparent;
+ }
+ }
+ }
+
+ .pagination {
+ display: grid;
+ grid-template-columns: 1fr repeat(7, auto);
+ height: 100%;
+ align-items: center;
+ column-gap: 2rem;
+ padding: 1rem;
+ margin: 0 5%;
+ color: vars.$linkblue;
+
+ .select {
+ color: black;
+
+ select {
+ margin: 0 0.5rem;
+ padding: 0.25rem;
+ border: 1px solid vars.$medgrey;
+ border-radius: 0.25rem;
+ }
+ }
+
+ .paginav {
+ font-weight: 600;
+ display: inline-grid;
+ grid-column-gap: 0.33rem;
+ align-items: center;
+ cursor: pointer;
+ transition: 0.25s;
+
+ &:hover {
+ color: vars.$hoverblue;
+ }
+
+ &.prev,
+ &.next {
+ grid-template-columns: auto 1fr;
+ }
+
+ &.first,
+ &.last {
+ grid-template-columns: auto auto 1fr;
+ }
+
+ &.no-click {
+ cursor: auto;
+
+ &:hover {
+ color: vars.$linkblue;
+ }
+ }
+
+ .chevron {
+ height: 0.75rem;
+ }
+ }
+
+ @media (max-width: 1000px) {
+ grid-template-columns: repeat(7, auto);
+ row-gap: 1rem;
+ column-gap: 1rem;
+
+ .select {
+ grid-column: 1 / span 7;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/components/EntityTable.tsx b/app/src/components/EntityTable.tsx
new file mode 100644
index 00000000..59baa69f
--- /dev/null
+++ b/app/src/components/EntityTable.tsx
@@ -0,0 +1,233 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useState, useEffect, useMemo, type ReactNode } from 'react';
+import './EntityTable.scss';
+
+interface EntityTableProps {
+ tableData: T[];
+ title?: string;
+ displaySearch?: boolean;
+ children: (entities: T[]) => ReactNode;
+}
+
+function EntityTable({ tableData, title = '', displaySearch = true, children }: EntityTableProps) {
+ const [entities, setEntities] = useState([]);
+ const [searchString, setSearchString] = useState('');
+ const [currentPage, setCurrentPage] = useState(1);
+ const [pagination, setPagination] = useState(10);
+
+ useEffect(() => {
+ setEntities(tableData);
+ }, [tableData]);
+
+ // Reset to first page when pagination changes
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [pagination]);
+
+ const searchArray = useMemo(() => {
+ return searchString.split(' ').filter(term => term.trim() !== '');
+ }, [searchString]);
+
+ const filteredEntities = useMemo(() => {
+ if (!entities) return [];
+ let filtered = entities;
+ if (!searchString) return filtered;
+
+ if (searchArray.length > 0) {
+ filtered = entities.filter(entity => {
+ const matching = Array(searchArray.length).fill(false);
+ const fields = entity as Record;
+
+ searchArray.forEach((param, pIdx) => {
+ Object.keys(fields).forEach(key => {
+ const value = fields[key];
+ const stringified = value == null ? 'null' : String(value);
+ if (stringified.toLowerCase().includes(param.toLowerCase())) {
+ matching[pIdx] = true;
+ }
+ });
+ });
+
+ return !matching.includes(false);
+ });
+ }
+
+ return filtered;
+ }, [entities, searchArray, searchString]);
+
+ const pagiBottom = useMemo(() => {
+ return (currentPage - 1) * pagination;
+ }, [currentPage, pagination]);
+
+ const pagiTop = useMemo(() => {
+ return currentPage * pagination;
+ }, [currentPage, pagination]);
+
+ const paginatedEntities = useMemo(() => {
+ return filteredEntities.slice(pagiBottom, pagiTop);
+ }, [filteredEntities, pagiBottom, pagiTop]);
+
+ const lastPage = useMemo(() => {
+ if (pagination && filteredEntities) {
+ return Math.ceil((filteredEntities.length || 1) / pagination);
+ }
+ return 1;
+ }, [pagination, filteredEntities]);
+
+ const pagiPrev = useMemo(() => {
+ return currentPage === 1 ? lastPage : currentPage - 1;
+ }, [currentPage, lastPage]);
+
+ const pagiNext = useMemo(() => {
+ return currentPage === lastPage ? 1 : currentPage + 1;
+ }, [currentPage, lastPage]);
+
+ const handlePageChange = (page: number) => {
+ if (page >= 1 && page <= lastPage) {
+ setCurrentPage(page);
+ }
+ };
+
+ if (!entities.length) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {displaySearch && (
+
+ Search:
+ setSearchString(e.target.value)}
+ />
+
+ )}
+
+
+
{children(paginatedEntities)}
+
+ {pagination && entities.length > 0 && (
+
+
+ Showing{' '}
+ {' '}
+ of {filteredEntities.length} entries.
+
+
+
handlePageChange(1)}
+ >
+
+
+
First
+
+
+
handlePageChange(pagiPrev)}
+ >
+
+
Previous
+
+
+
1 ? '' : 'no-click'}`}
+ onClick={() => currentPage > 1 && handlePageChange(currentPage - 1)}
+ >
+ {currentPage > 1 ? currentPage - 1 : '...'}
+
+
+
+ {currentPage}
+
+
+
currentPage < lastPage && handlePageChange(currentPage + 1)}
+ >
+ {currentPage < lastPage ? currentPage + 1 : '...'}
+
+
+
handlePageChange(pagiNext)}
+ >
+
Next
+
+
+
+
handlePageChange(lastPage)}
+ >
+
Last
+
+
+
+
+ )}
+
+
+
+ );
+}
+
+export default EntityTable;
diff --git a/app/src/components/EntityTable.vue b/app/src/components/EntityTable.vue
deleted file mode 100644
index 6a37a914..00000000
--- a/app/src/components/EntityTable.vue
+++ /dev/null
@@ -1,235 +0,0 @@
-
-
-
-
-
-.table-main
- .table-container
- .header
- .title-holder
- .title {{ title }}
- .search-area(v-if='displaySearch')
- label(for="search") Search:
- input(
- type="text"
- id="search"
- v-model='searchString'
- )
- .content
- .entities-holder
- slot(:entities='paginatedEntities')
- .pagination(
- v-if='pagination && entities'
- )
- .select Showing
- select(
- name="pagination"
- id="pagination"
- v-model='pagination'
- )
- option(value=10) 10
- option(value=25) 25
- option(value=50) 50
- option(value=100) 100
- | of {{filteredEntities.length}} entries.
- .paginav.first(@click='currentPage = 1')
- img.chevron(src='/img/arrowL.png')
- img.chevron(src='/img/arrowL.png')
- span.word First
- .prev.paginav(@click='currentPage = pagiPrev')
- img.chevron(src='/img/arrowL.png')
- span.word Previous
- .paginav(
- @click='currentPage = currentPage > 1 ? currentPage - 1 : currentPage'
- :class='currentPage > 1 ? "" : "no-click"'
- )
- span.word {{ currentPage > 1 ? currentPage - 1 : "..."}}
- .paginav.no-click
- span.word {{ currentPage }}
- .paginav(
- @click='currentPage = currentPage < lastPage ? currentPage + 1 : currentPage'
- :class='currentPage < lastPage ? "" : "no-click"'
- )
- span.word {{ currentPage < lastPage ? currentPage + 1 : "..."}}
- .next.paginav(@click='currentPage = pagiNext')
- span.word Next
- img.chevron(src='/img/arrowR.png')
- .paginav.last(@click='currentPage = lastPage')
- span.word Last
- img.chevron(src='/img/arrowR.png')
- img.chevron(src='/img/arrowR.png')
-
-
-
diff --git a/app/src/components/FloatingModal.scss b/app/src/components/FloatingModal.scss
new file mode 100644
index 00000000..7fe4d914
--- /dev/null
+++ b/app/src/components/FloatingModal.scss
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.modal-main {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1000;
+
+ .modal-background {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background-color: #00000088;
+ }
+
+ .modal-holder {
+ position: relative;
+ height: 100%;
+ pointer-events: none;
+
+ .modal-container {
+ position: absolute;
+ max-width: 80%;
+ min-width: 50%;
+ height: 60vh;
+ background-color: white;
+ border-radius: 0.25rem;
+ margin: auto;
+ padding: 1rem 0;
+ top: 50%;
+ transform: translateY(-50%) translateX(-50%);
+ left: 50%;
+ pointer-events: auto;
+
+ .header {
+ font-weight: 700;
+ font-size: 1.5rem;
+ display: grid;
+ grid-template-columns: 1fr auto;
+ border-bottom: 2px solid black;
+ padding: 0 1rem 1rem;
+
+ .close {
+ cursor: pointer;
+ }
+ }
+
+ .body {
+ padding: 1rem 1rem 0;
+ overflow-y: auto;
+ height: calc(100% - 3.5rem);
+ }
+ }
+ }
+}
diff --git a/app/src/components/FloatingModal.tsx b/app/src/components/FloatingModal.tsx
new file mode 100644
index 00000000..266a9ba4
--- /dev/null
+++ b/app/src/components/FloatingModal.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { type ReactNode } from 'react';
+import './FloatingModal.scss';
+
+interface FloatingModalProps {
+ title: string;
+ children: ReactNode;
+ onCloseModal: () => void;
+}
+
+const FloatingModal: React.FC = ({ title, children, onCloseModal }) => {
+ return (
+
+ );
+};
+
+export default FloatingModal;
diff --git a/app/src/components/FloatingModal.vue b/app/src/components/FloatingModal.vue
deleted file mode 100644
index 1b0f034c..00000000
--- a/app/src/components/FloatingModal.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
-.modal-main
- .modal-background(@click='closeModal()')
- .modal-holder
- .modal-container
- .header
- .title {{title}}
- .close(@click='closeModal()') ✕
- .body
- slot
-
-
-
diff --git a/app/src/components/HeaderNav.scss b/app/src/components/HeaderNav.scss
new file mode 100644
index 00000000..e0d3d506
--- /dev/null
+++ b/app/src/components/HeaderNav.scss
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars.scss' as vars;
+
+// BEM Block: header-nav
+.header-nav {
+ background-color: white !important;
+
+ // BEM Element: header-nav__container
+ &__container {
+ display: flex;
+ flex-wrap: inherit;
+ align-items: center;
+ width: 90%;
+ margin: auto;
+ padding: 0.5rem 0;
+ }
+
+ // BEM Element: header-nav__brand
+ &__brand {
+ padding: 0.5rem 0;
+ }
+
+ // BEM Element: header-nav__nav
+ &__nav {
+ flex-direction: row;
+ }
+
+ // BEM Element: header-nav__nav-list
+ &__nav-list {
+ margin-left: 1rem;
+ }
+
+ // BEM Element: header-nav__nav-item
+ &__nav-item {
+ display: flex;
+ }
+
+ // BEM Element: header-nav__link
+ &__link {
+ padding: 0.5rem;
+ text-decoration: none;
+ transition: 0.15s;
+ color: vars.$linkblue;
+
+ // BEM Modifier: header-nav__link--hover
+ &:hover {
+ color: vars.$hoverblue;
+ }
+ }
+}
diff --git a/app/src/components/HeaderNav.tsx b/app/src/components/HeaderNav.tsx
new file mode 100644
index 00000000..17148271
--- /dev/null
+++ b/app/src/components/HeaderNav.tsx
@@ -0,0 +1,83 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Link } from 'react-router-dom';
+import './HeaderNav.scss';
+
+const HeaderNav: React.FC = () => {
+ return (
+
+
+
+
+
+
+
+
+
+ Browse
+
+
+ Search
+
+
+
+
+
+ About
+
+
+
+
+
+
+ );
+};
+
+export default HeaderNav;
diff --git a/app/src/components/HeaderNav.vue b/app/src/components/HeaderNav.vue
deleted file mode 100644
index 325850d0..00000000
--- a/app/src/components/HeaderNav.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-nav.navbar.navbar-expand-lg.bg-light
- .container
- a.navbar-brand(href="/")
- img(
- src="https://raw.githubusercontent.com/Open-Reaction-Database/ord-schema/main/logos/logo.svg"
- alt="ORD Logo"
- height="30"
- )
- #navbarNav.collapse.navbar-collapse
- .navbar-nav
- .nav-item
- router-link.nav-link(:to='{name: "browse"}') Browse
- router-link.nav-link(:to='{name: "search"}') Search
- .nav-item
- a.nav-link(href="https://app.open-reaction-database.org") Contribute
- a.nav-link(href="https://docs.open-reaction-database.org") Docs
- .nav-item
- router-link.nav-link(:to='{name: "about"}') About
-
-
-
diff --git a/app/src/components/LoadingSpinner.scss b/app/src/components/LoadingSpinner.scss
new file mode 100644
index 00000000..8ab09b5c
--- /dev/null
+++ b/app/src/components/LoadingSpinner.scss
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars.scss' as vars;
+
+.spinner-main {
+ width: 100%;
+ height: 100%;
+ display: flex;
+
+ .loader-ring {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+ margin: auto;
+
+ div {
+ box-sizing: border-box;
+ display: block;
+ position: absolute;
+ width: 64px;
+ height: 64px;
+ margin: 8px;
+ border: 8px solid vars.$linkblue;
+ border-radius: 50%;
+ animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ border-color: vars.$linkblue transparent transparent transparent;
+
+ &:nth-child(1) {
+ animation-delay: -0.45s;
+ }
+
+ &:nth-child(2) {
+ animation-delay: -0.3s;
+ }
+
+ &:nth-child(3) {
+ animation-delay: -0.15s;
+ }
+ }
+ }
+}
+
+@keyframes lds-ring {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/app/src/utils/outcomes.js b/app/src/components/LoadingSpinner.tsx
similarity index 64%
rename from app/src/utils/outcomes.js
rename to app/src/components/LoadingSpinner.tsx
index 0a6adc94..f3d0e71d 100644
--- a/app/src/utils/outcomes.js
+++ b/app/src/components/LoadingSpinner.tsx
@@ -14,13 +14,20 @@
* limitations under the License.
*/
-import reaction_pb from 'ord-schema';
+import React from 'react';
+import './LoadingSpinner.scss';
-export default {
- formattedTime(timeData) {
- if (!timeData) return null;
- const timeUnits = reaction_pb.Time.TimeUnit;
- const type = Object.keys(timeUnits).find(key => timeUnits[key] == timeData.units);
- return `${timeData.value} ${type.toLowerCase()}${timeData.units !== 0 ? '(s)' : ''}`;
- },
+const LoadingSpinner: React.FC = () => {
+ return (
+
+ );
};
+
+export default LoadingSpinner;
diff --git a/app/src/components/LoadingSpinner.vue b/app/src/components/LoadingSpinner.vue
deleted file mode 100644
index 3a2aee94..00000000
--- a/app/src/components/LoadingSpinner.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-.spinner-main
- transition(
- name='fade'
- )
- .loader-ring
- div
- div
- div
- div
-
-
-
diff --git a/app/src/components/MainFooter.scss b/app/src/components/MainFooter.scss
new file mode 100644
index 00000000..9333aca7
--- /dev/null
+++ b/app/src/components/MainFooter.scss
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars.scss' as vars;
+
+.main-footer {
+ background-color: vars.$lightgrey;
+ padding: 2rem 0;
+ margin-top: 4rem;
+
+ &__container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 1rem;
+ }
+
+ &__content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 1rem;
+ }
+
+ &__text {
+ color: #666;
+ margin: 0;
+ font-size: 0.9rem;
+ }
+
+ &__links {
+ display: flex;
+ gap: 1.5rem;
+ align-items: center;
+ }
+
+ &__link {
+ color: vars.$linkblue;
+ text-decoration: none;
+ font-size: 0.9rem;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: vars.$hoverblue;
+ text-decoration: underline;
+ }
+ }
+
+ &__logo {
+ height: 24px;
+ width: auto;
+ }
+}
+
+@media (max-width: 768px) {
+ .main-footer {
+ &__content {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ &__links {
+ justify-content: center;
+ }
+ }
+}
diff --git a/app/src/components/MainFooter.tsx b/app/src/components/MainFooter.tsx
new file mode 100644
index 00000000..9bea1b8a
--- /dev/null
+++ b/app/src/components/MainFooter.tsx
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import './MainFooter.scss';
+
+const MainFooter: React.FC = () => {
+ return (
+
+
+
+
© 2024 Open Reaction Database Project
+
+
+
+
+ );
+};
+
+export default MainFooter;
diff --git a/app/src/components/MainFooter.vue b/app/src/components/MainFooter.vue
deleted file mode 100644
index 603b31bb..00000000
--- a/app/src/components/MainFooter.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-footer.bg-light.text-center.mt-auto
- div.p-3.copy © Copyright 2023 Open Reaction Database Project Authors
-
-
-
diff --git a/app/src/components/ModalKetcher.scss b/app/src/components/ModalKetcher.scss
new file mode 100644
index 00000000..e1532b56
--- /dev/null
+++ b/app/src/components/ModalKetcher.scss
@@ -0,0 +1,116 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/transition';
+
+.background {
+ width: 100%;
+ height: 100%;
+ z-index: 1000;
+ background-color: rgba(0, 0, 0, 0.6);
+ position: fixed;
+ top: 0;
+ left: 0;
+
+ .modal {
+ width: 75vw;
+ height: 75vh;
+ background-color: white;
+ border-radius: 0.25rem;
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ overflow: hidden;
+ max-width: 1000px;
+ max-height: 800px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+
+ .modal-content {
+ width: 100%;
+ height: 100%;
+ display: grid;
+ grid-template-rows: 1fr auto;
+ position: relative;
+
+ .modal-body {
+ width: 100%;
+ height: 100%;
+
+ iframe {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ }
+ }
+
+ .modal-footer {
+ padding: 1rem;
+ border-top: 2px solid #eff2f5; // matches ketcher bottom bar
+ display: flex;
+ justify-content: flex-end;
+ column-gap: 1rem;
+ background-color: white;
+
+ button {
+ padding: 0.5rem 1rem;
+ border: 1px solid #ccc;
+ border-radius: 0.25rem;
+ background-color: white;
+ cursor: pointer;
+ transition: 0.2s;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ &:first-child {
+ color: black;
+ }
+
+ &:last-child {
+ background-color: #007bff;
+ color: white;
+ border-color: #007bff;
+
+ &:hover:not(:disabled) {
+ background-color: #0056b3;
+ }
+ }
+ }
+ }
+
+ .modal-loading {
+ background-color: white;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10;
+ }
+ }
+ }
+}
diff --git a/app/src/components/ModalKetcher.tsx b/app/src/components/ModalKetcher.tsx
new file mode 100644
index 00000000..58a151e6
--- /dev/null
+++ b/app/src/components/ModalKetcher.tsx
@@ -0,0 +1,192 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect, useCallback } from 'react';
+import LoadingSpinner from './LoadingSpinner';
+import './ModalKetcher.scss';
+
+interface ModalKetcherProps {
+ smiles: string;
+ onUpdateSmiles: (smiles: string) => void;
+ onCloseModal: () => void;
+}
+
+// Subset of the Ketcher API surface we actually invoke.
+interface KetcherApi {
+ setMolecule(molfile: string): void;
+ getSmiles(): Promise;
+}
+
+interface KetcherWindow extends Window {
+ ketcher?: KetcherApi;
+}
+
+const ModalKetcher: React.FC = ({ smiles, onUpdateSmiles, onCloseModal }) => {
+ const [contWin, setContWin] = useState(null);
+ const [mutatedSmiles, setMutatedSmiles] = useState('');
+ const [loading, setLoading] = useState(true);
+
+ const drawSmiles = useCallback(async () => {
+ if (mutatedSmiles && contWin?.ketcher) {
+ try {
+ const response = await fetch(`/api/molfile?smiles=${encodeURIComponent(mutatedSmiles)}`);
+ if (response.ok) {
+ const molfile = (await response.json()) as string;
+ contWin.ketcher.setMolecule(molfile);
+ } else {
+ console.warn('Failed to get molfile for SMILES:', mutatedSmiles);
+ }
+ } catch (error) {
+ console.error('Error loading molecule into Ketcher:', error);
+ }
+ }
+ }, [mutatedSmiles, contWin]);
+
+ const saveSmiles = useCallback(async () => {
+ if (contWin?.ketcher) {
+ try {
+ const newSmiles = await contWin.ketcher.getSmiles();
+ setMutatedSmiles(newSmiles);
+ onUpdateSmiles(newSmiles);
+ onCloseModal();
+ } catch (error) {
+ console.error('Error getting SMILES from Ketcher:', error);
+ }
+ }
+ }, [contWin, onUpdateSmiles, onCloseModal]);
+
+ const handleBackgroundClick = (e: React.MouseEvent) => {
+ // Close modal if clicking on background (not on modal content)
+ if (e.target === e.currentTarget) {
+ onCloseModal();
+ }
+ };
+
+ const handleKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ // Close modal on Escape key
+ if (e.key === 'Escape') {
+ onCloseModal();
+ }
+ },
+ [onCloseModal],
+ );
+
+ useEffect(() => {
+ setMutatedSmiles(smiles);
+ }, [smiles]);
+
+ // Poll the iframe's contentWindow once per second until it exposes
+ // `ketcher`. Runs once on mount with no React state in the deps array so
+ // success doesn't kick off a fresh 30-second idle interval (the previous
+ // useCallback-based version re-fired on every relevant state change).
+ useEffect(() => {
+ const intervalId = window.setInterval(() => {
+ const iframe = document.getElementById('ketcher-iframe') as HTMLIFrameElement | null;
+ if (!iframe?.contentWindow) return;
+ try {
+ const win = iframe.contentWindow as KetcherWindow;
+ if (win.ketcher) {
+ window.clearInterval(intervalId);
+ window.clearTimeout(timeoutId);
+ setContWin(win);
+ setLoading(false);
+ }
+ } catch {
+ // Cross-origin or other access issues — keep polling.
+ }
+ }, 1000);
+
+ const timeoutId = window.setTimeout(() => {
+ window.clearInterval(intervalId);
+ setLoading(prev => {
+ if (prev) console.warn('Ketcher failed to load within 30 seconds');
+ return false;
+ });
+ }, 30000);
+
+ return () => {
+ window.clearInterval(intervalId);
+ window.clearTimeout(timeoutId);
+ };
+ }, []);
+
+ useEffect(() => {
+ // Add keyboard event listener
+ document.addEventListener('keydown', handleKeyDown);
+ return () => {
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [handleKeyDown]);
+
+ // Re-trigger drawSmiles when contWin becomes available
+ useEffect(() => {
+ if (contWin && mutatedSmiles) {
+ drawSmiles();
+ }
+ }, [contWin, drawSmiles, mutatedSmiles]);
+
+ return (
+
+
+
+
+ {/* Point at Ketcher's own index.html (shipped in the standalone
+ bundle under app/public/ketcher/). All of Ketcher's bundled
+ asset paths are relative — sourcing from `/ketcher/index.html`
+ lets the iframe's document resolve them against
+ `/ketcher/static/...` correctly. Pointing the iframe at our
+ React Router route instead (the previous design) loaded our
+ shell HTML, which broke Ketcher's relative asset resolution. */}
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+ {loading && (
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default ModalKetcher;
diff --git a/app/src/components/ModalKetcher.vue b/app/src/components/ModalKetcher.vue
deleted file mode 100644
index e8a36ec3..00000000
--- a/app/src/components/ModalKetcher.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-
-
-
-
-
-.background
- #ketcher_modal.modal
- .modal-content
- .modal-body
- iframe#ketcher-iframe( src='/ketcher' )
- .modal-footer
- button(
- type='button'
- @click='this.$emit("closeModal")'
- ) Cancel
- button(
- type='button'
- @click='saveSmiles()'
- ) Save
- transition(name="fade")
- .modal-loading(
- v-if='loading'
- )
- LoadingSpinner
-
-
-
diff --git a/app/src/components/ReactionCard.scss b/app/src/components/ReactionCard.scss
new file mode 100644
index 00000000..48c6ecc4
--- /dev/null
+++ b/app/src/components/ReactionCard.scss
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars' as vars;
+
+.reaction-container {
+ text-decoration: none;
+
+ .row {
+ background-color: white;
+ border-radius: 0.25rem;
+ padding: 0.75rem;
+ margin-bottom: 1rem;
+ transition: 0.25s;
+ border: 4px solid white;
+
+ &:hover {
+ box-shadow: 0 0 5px vars.$darkgrey;
+ }
+
+ .select {
+ input,
+ label {
+ cursor: pointer;
+ }
+ }
+
+ .reaction-table {
+ color: black;
+ overflow-x: wrap;
+ }
+
+ .is-mined {
+ display: flex;
+ flex-direction: row-reverse;
+ }
+
+ .is-mined-badge {
+ text-align: center;
+ border-radius: 5px;
+ background-color: #0d6efd;
+ color: #fff;
+ font-size: 0.8rem;
+ width: 60px;
+ }
+
+ .info {
+ display: grid;
+ grid-template-columns: repeat(2, 50%);
+ row-gap: 0.5rem;
+ column-gap: 1rem;
+ margin-top: 1rem;
+
+ .col.full {
+ grid-column: 1/3;
+
+ button {
+ font-size: 1.2rem;
+ }
+ }
+
+ .col {
+ * {
+ margin-top: 0.5rem;
+ }
+ }
+
+ .smile {
+ display: flex;
+ column-gap: 0.5rem;
+ align-items: center;
+ margin-top: 0.25rem;
+
+ .value {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+
+ &.selected {
+ border-color: vars.$linkblue;
+ }
+ }
+}
diff --git a/app/src/components/ReactionCard.tsx b/app/src/components/ReactionCard.tsx
new file mode 100644
index 00000000..e861521b
--- /dev/null
+++ b/app/src/components/ReactionCard.tsx
@@ -0,0 +1,207 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useEffect, useState, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
+import reaction_pb from 'ord-schema';
+import type { CompoundIdentifier, ProductMeasurement } from 'ord-schema/proto/reaction_pb';
+import LoadingSpinner from './LoadingSpinner';
+import CopyButton from './CopyButton';
+import { enumName } from '../utils/enum';
+import { formatPercentage } from '../utils/outcomes';
+import type { SearchResult, ReactionData } from '../types/search';
+import './ReactionCard.scss';
+
+interface ReactionCardProps {
+ reaction: SearchResult;
+ isSelectable?: boolean;
+ isSelected?: boolean;
+ onSelectionChange?: (reactionId: string, isSelected: boolean) => void;
+}
+
+const YIELD_MEASUREMENT_TYPE = reaction_pb.ProductMeasurement.ProductMeasurementType.YIELD;
+
+const ReactionCard: React.FC = ({
+ reaction,
+ isSelectable = true,
+ isSelected = false,
+ onSelectionChange,
+}) => {
+ const navigate = useNavigate();
+ const [reactionTable, setReactionTable] = useState(null);
+
+ const getReactionTable = useCallback(async () => {
+ try {
+ const response = await fetch(`/api/reaction_summary?reaction_id=${reaction.reaction_id}`);
+ // Skip the 4xx/5xx body — it's an HTML error page that
+ // dangerouslySetInnerHTML would render verbatim in every card.
+ if (!response.ok) {
+ console.error(`reaction_summary failed (HTTP ${response.status}) for ${reaction.reaction_id}`);
+ return;
+ }
+ setReactionTable(await response.text());
+ } catch (error) {
+ console.error('Error fetching reaction table:', error);
+ }
+ }, [reaction.reaction_id]);
+
+ const getYield = (measurements: ProductMeasurement.AsObject[] = []): string => {
+ const yieldObj = measurements.find(m => m.type === YIELD_MEASUREMENT_TYPE);
+ return yieldObj?.percentage ? formatPercentage(yieldObj.percentage) : 'Not listed';
+ };
+
+ const getConversion = (data: ReactionData | undefined): string => {
+ const conversion = data?.outcomesList?.[0]?.conversion;
+ if (!conversion) return 'Not listed';
+ return formatPercentage(conversion);
+ };
+
+ const conditionsAndDuration = (data: ReactionData | undefined): string[] => {
+ const details: string[] = [];
+ if (!data) return details;
+
+ const temp = data.conditions?.temperature?.setpoint;
+ if (temp) {
+ const units = enumName(reaction_pb.Temperature.TemperatureUnit, temp.units);
+ details.push(`at ${temp.value}${units ? ` ${units.toLowerCase()}` : '°C'}`);
+ }
+
+ const pressure = data.conditions?.pressure?.setpoint;
+ if (pressure) {
+ const units = enumName(reaction_pb.Pressure.PressureUnit, pressure.units);
+ details.push(`under ${pressure.value}${units ? ` ${units.toLowerCase()}` : ' atm'}`);
+ }
+
+ const reactionTime = data.outcomesList?.[0]?.reactionTime;
+ if (reactionTime?.value) {
+ const units = enumName(reaction_pb.Time.TimeUnit, reactionTime.units);
+ details.push(`for ${reactionTime.value}${units ? ` ${units.toLowerCase()}` : 's'}`);
+ }
+
+ return details;
+ };
+
+ const productIdentifier = (identifier: CompoundIdentifier.AsObject): string => {
+ const type = enumName(reaction_pb.CompoundIdentifier.CompoundIdentifierType, identifier.type);
+ return `${type ?? ''}: ${identifier.value}`;
+ };
+
+ const handleCheckboxChange = (event: React.ChangeEvent) => {
+ if (onSelectionChange) {
+ onSelectionChange(reaction.reaction_id, event.target.checked);
+ }
+ };
+
+ const handleViewDetails = () => {
+ navigate(`/id/${reaction.reaction_id}`);
+ };
+
+ useEffect(() => {
+ getReactionTable();
+ }, [getReactionTable]);
+
+ const reactionData = reaction.data;
+ const firstOutcome = reactionData?.outcomesList?.[0];
+ const firstProduct = firstOutcome?.productsList?.[0];
+ const firstProductIdentifier = firstProduct?.identifiersList?.[0];
+ const provenance = reactionData?.provenance;
+
+ return (
+
+
+ {isSelectable && (
+
+
+ Select reaction
+
+ )}
+
+ {provenance?.isMined && (
+
+ )}
+
+
+
+
+
+ View Full Details
+
+
+
+
Yield: {getYield(firstProduct?.measurementsList || [])}
+
Conversion: {getConversion(reactionData)}
+
+ Conditions: {conditionsAndDuration(reactionData).join('; ') || 'Not Listed'}
+
+ {firstProductIdentifier && (
+
+
+
Product {productIdentifier(firstProductIdentifier)}
+
+ )}
+
+
+
+
+ Uploaded by {provenance?.recordCreated?.person?.name || 'Unknown'},{' '}
+ {provenance?.recordCreated?.person?.organization || 'Unknown'}
+
+
+ Uploaded on{' '}
+ {provenance?.recordCreated?.time?.value
+ ? new Date(provenance.recordCreated.time.value).toLocaleDateString()
+ : 'Unknown'}
+
+
DOI: {provenance?.doi || 'Not available'}
+ {provenance?.publicationUrl && (
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default ReactionCard;
diff --git a/app/src/components/ReactionCard.vue b/app/src/components/ReactionCard.vue
deleted file mode 100644
index a04c1b08..00000000
--- a/app/src/components/ReactionCard.vue
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
-
-.reaction-container
- .row(:class='isSelected ? "selected" : ""')
- .select(v-if='isSelectable')
- input(
- type="checkbox"
- :id='"select_"+reaction.reaction_id'
- :value='reaction.reaction_id'
- :checked='isSelected'
- @change='$emit("clickedSelect", $event)'
- )
- label(:for='"select_"+reaction.reaction_id') Select reaction
- .is-mined(v-if='reaction.data.provenance.isMined')
- .is-mined-badge Mined
- .reaction-table(
- v-html='reactionTable'
- v-if='reactionTable'
- )
- LoadingSpinner(v-else)
- .info
- .col.full
- router-link(
- :to='{ name: "reaction-view", params: {reactionId: reaction.reaction_id}}'
- )
- button View Full Details
- .col
- .yield Yield: {{getYield(reaction.data.outcomesList[0].productsList[0].measurementsList)}}
- .conversion Conversion: {{getConversion(reaction.data)}}
- .conditions Conditions: {{conditionsAndDuration(reaction.data).join("; ") || "Not Listed"}}
- .smile(v-if='reaction.data.outcomesList[0].productsList[0].identifiersList.length')
- CopyButton(
- :textToCopy='reaction.data.outcomesList[0].productsList[0].identifiersList[0].value'
- )
- .value Product {{productIdentifier(reaction.data.outcomesList[0].productsList[0].identifiersList[0])}}
- .col
- .creator Uploaded by {{reaction.data.provenance.recordCreated.person.name}}, {{reaction.data.provenance.recordCreated.person.organization}}
- .date Uploaded on {{new Date(reaction.data.provenance.recordCreated.time.value).toLocaleDateString()}}
- .doi DOI: {{reaction.data.provenance.doi}}
- .publication
- a(
- :href='reaction.data.provenance.publicationUrl'
- target="_blank"
- ) Publication URL
- .dataset Dataset:
- a(
- :href='"/search?dataset_id=" + reaction.dataset_id + "&limit=100"'
- target="_blank"
- ) {{reaction.dataset_id}}
-
-
-
diff --git a/app/src/hooks/useSearchTask.ts b/app/src/hooks/useSearchTask.ts
new file mode 100644
index 00000000..7ae44685
--- /dev/null
+++ b/app/src/hooks/useSearchTask.ts
@@ -0,0 +1,117 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useRef } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import reaction_pb from 'ord-schema';
+import { base64ToBytes } from '../utils/base64';
+import type { SearchResult } from '../types/search';
+
+const POLL_INTERVAL_MS = 1000;
+const POLL_TIMEOUT_MS = 120_000;
+
+type TaskState = { status: 'success'; results: SearchResult[] } | { status: 'pending'; taskId: string };
+
+interface TaskRef {
+ queryString: string | null;
+ taskId: string | null;
+ // In-flight submit_query promise. Two queryFn invocations can overlap
+ // (StrictMode dev double-invoke, react-query refetch racing a polling
+ // refetch, etc.); holding the in-flight promise on the ref lets the second
+ // caller await the first one's result instead of firing a duplicate
+ // submit_query that leaves an orphaned task on the backend.
+ submitPromise: Promise | null;
+ startTime: number;
+}
+
+/**
+ * Runs the API's submit-query / poll-result protocol against the given query
+ * string, returning the materialized search results once the task completes.
+ *
+ * Submits to `/api/submit_query` exactly once per `queryString`
+ * change, then polls `/api/fetch_query_result?task_id=…` every second until the
+ * server returns 200. Gives up after `POLL_TIMEOUT_MS` to bound user wait.
+ */
+export function useSearchTask(queryString: string | null, enabled: boolean) {
+ // All polling state lives on this single ref, keyed by the queryString that
+ // owns it, so a queryString change is the *only* reset signal. An earlier
+ // version reset taskId / startTime from a separate useEffect; under
+ // dev the effect was re-running after queryFn had already set
+ // startTime, leaving it at 0 — and "Date.now() - 0 > 120s" tripped the
+ // timeout on the very first poll iteration.
+ const taskRef = useRef({ queryString: null, taskId: null, submitPromise: null, startTime: 0 });
+
+ return useQuery({
+ queryKey: ['search-task', queryString],
+ enabled: enabled && queryString !== null,
+ retry: false,
+ staleTime: Infinity,
+ refetchInterval: query => (query.state.data?.status === 'pending' ? POLL_INTERVAL_MS : false),
+ refetchIntervalInBackground: false,
+ queryFn: async (): Promise => {
+ if (!queryString) return { status: 'success', results: [] };
+
+ // queryString changed since the last call — start fresh.
+ if (taskRef.current.queryString !== queryString) {
+ taskRef.current = { queryString, taskId: null, submitPromise: null, startTime: 0 };
+ }
+
+ if (taskRef.current.taskId === null) {
+ if (!taskRef.current.submitPromise) {
+ taskRef.current.startTime = Date.now();
+ taskRef.current.submitPromise = (async () => {
+ const submitRes = await fetch(`/api/submit_query${queryString}`);
+ if (!submitRes.ok) {
+ throw new Error(`submit_query failed (HTTP ${submitRes.status})`);
+ }
+ return (await submitRes.json()) as string;
+ })();
+ }
+ try {
+ taskRef.current.taskId = await taskRef.current.submitPromise;
+ } finally {
+ taskRef.current.submitPromise = null;
+ }
+ }
+
+ if (Date.now() - taskRef.current.startTime > POLL_TIMEOUT_MS) {
+ const id = taskRef.current.taskId;
+ taskRef.current.taskId = null;
+ throw new Error(`Search task ${id} timed out after ${POLL_TIMEOUT_MS / 1000}s`);
+ }
+
+ const res = await fetch(`/api/fetch_query_result?task_id=${taskRef.current.taskId}`);
+
+ if (res.status === 200) {
+ const raw = (await res.json()) as Omit[];
+ const results: SearchResult[] = raw.map(r => ({
+ ...r,
+ data: reaction_pb.Reaction.deserializeBinary(new Uint8Array(base64ToBytes(r.proto))).toObject(),
+ }));
+ taskRef.current.taskId = null;
+ return { status: 'success', results };
+ }
+
+ if (res.status === 202) {
+ return { status: 'pending', taskId: taskRef.current.taskId };
+ }
+
+ const id = taskRef.current.taskId;
+ taskRef.current.taskId = null;
+ throw new Error(`Search task ${id} failed (HTTP ${res.status})`);
+ },
+ });
+}
diff --git a/app/src/index.scss b/app/src/index.scss
new file mode 100644
index 00000000..7f4d2c56
--- /dev/null
+++ b/app/src/index.scss
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Global CSS reset and base styles for the React application
+ */
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+#root {
+ min-height: 100vh;
+}
diff --git a/app/src/main.tsx b/app/src/main.tsx
new file mode 100644
index 00000000..27694d83
--- /dev/null
+++ b/app/src/main.tsx
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import './index.scss';
+import App from './App.tsx';
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/app/src/router/index.js b/app/src/router/index.js
deleted file mode 100644
index c4b599db..00000000
--- a/app/src/router/index.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * Copyright 2023 Open Reaction Database Project Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { createRouter, createWebHistory } from 'vue-router';
-
-import About from '../views/About.vue';
-import Home from '../views/Home.vue';
-
-const routes = [
- { path: '/', name: 'home', component: Home },
- { path: '/about', name: 'about', component: About },
- {
- path: '/browse',
- name: 'browse',
- component: () => import('../views/browse/MainBrowse.vue'),
- },
- {
- path: '/browse/set',
- name: 'selected-set',
- component: () => import('../views/browse/selected-set/MainSelectedSet.vue'),
- },
- {
- path: '/search',
- name: 'search',
- component: () => import('../views/search/MainSearch.vue'),
- },
- {
- path: '/dataset/:datasetId',
- name: 'dataset-view',
- component: () => import('../views/dataset-view/MainDatasetView.vue'),
- },
- {
- path: '/id/:reactionId',
- name: 'reaction-view',
- component: () => import('../views/reaction-view/MainReactionView.vue'),
- },
- {
- path: '/ketcher',
- name: 'ketcher',
- component: () => import('../views/viewKetcher/MainKetcher.vue'),
- },
-];
-
-const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes });
-
-export default router;
diff --git a/app/src/styles/table.scss b/app/src/styles/table.scss
new file mode 100644
index 00000000..1b0af6fe
--- /dev/null
+++ b/app/src/styles/table.scss
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars' as vars;
+
+.content {
+ .table-container {
+ background-color: white;
+ width: 90%;
+ margin: auto;
+ border-radius: 0.5rem;
+ display: grid;
+ overflow: hidden;
+
+ .column {
+ border-top: 1px solid vars.$medgrey;
+ padding: 0.5rem;
+
+ &.label {
+ border-top: none;
+ font-weight: 700;
+ font-size: 1.25rem;
+ }
+ }
+ }
+}
diff --git a/app/src/styles/tabs.sass b/app/src/styles/tabs.sass
deleted file mode 100644
index 691addb1..00000000
--- a/app/src/styles/tabs.sass
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright 2023 Open Reaction Database Project Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@use './vars' as *
-.tabs
- display: flex
- column-gap: 0.5rem
- row-gap: 0.5rem
- margin-bottom: 0.5rem
- flex-wrap: wrap
- .tab
- padding: 0.5rem 1rem
- border-radius: 0.25rem
- border: 1px solid $medgrey
- cursor: pointer
- transition: 0.25s
- &.selected
- background-color: $linkblue
- color: white
- border-color: $linkblue
- cursor: default
- &.capitalize
- text-transform: capitalize
\ No newline at end of file
diff --git a/app/src/styles/tabs.scss b/app/src/styles/tabs.scss
new file mode 100644
index 00000000..b85f1088
--- /dev/null
+++ b/app/src/styles/tabs.scss
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use './vars.scss' as vars;
+
+.tabs {
+ display: flex;
+ column-gap: 0.5rem;
+ row-gap: 0.5rem;
+ margin-bottom: 0.5rem;
+ flex-wrap: wrap;
+
+ .tab {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ border: 1px solid vars.$medgrey;
+ cursor: pointer;
+ transition: 0.25s;
+
+ &.selected {
+ background-color: vars.$linkblue;
+ color: white;
+ border-color: vars.$linkblue;
+ cursor: default;
+ }
+
+ &.capitalize {
+ text-transform: capitalize;
+ }
+ }
+}
diff --git a/app/src/styles/transition.scss b/app/src/styles/transition.scss
new file mode 100644
index 00000000..fd609335
--- /dev/null
+++ b/app/src/styles/transition.scss
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.expand-enter-active,
+.expand-leave-active {
+ transition: 0.5s ease;
+ max-height: 400px;
+ opacity: 1;
+}
+
+.expand-enter-from,
+.expand-leave-to {
+ max-height: 0;
+ overflow: hidden;
+ opacity: 0;
+}
+
+.expand-enter-to,
+.expand-leave-from {
+ max-height: 400px;
+ overflow: hidden;
+ opacity: 1;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.25s;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+ opacity: 0;
+}
diff --git a/app/src/styles/vars.sass b/app/src/styles/vars.scss
similarity index 86%
rename from app/src/styles/vars.sass
rename to app/src/styles/vars.scss
index a6154a2c..1c64d3f0 100644
--- a/app/src/styles/vars.sass
+++ b/app/src/styles/vars.scss
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-$lightgrey: #f8f9fa
-$medgrey: #e8e9e3
-$darkgrey: #a0a0a0
-$linkblue: #0d6efd
-$hoverblue: #0a58ca
\ No newline at end of file
+$lightgrey: #f8f9fa;
+$medgrey: #e8e9e3;
+$darkgrey: #a0a0a0;
+$linkblue: #0d6efd;
+$hoverblue: #0a58ca;
diff --git a/app/src/types/search.ts b/app/src/types/search.ts
new file mode 100644
index 00000000..b6b38cc9
--- /dev/null
+++ b/app/src/types/search.ts
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type {
+ Reaction,
+ ReactionConditions,
+ ReactionNotes,
+ ReactionObservation,
+ ReactionOutcome,
+ ReactionProvenance,
+ ReactionSetup,
+ ReactionWorkup,
+} from 'ord-schema/proto/reaction_pb';
+
+export type ReactionData = Reaction.AsObject;
+export type ReactionConditionsData = ReactionConditions.AsObject;
+export type ReactionNotesData = ReactionNotes.AsObject;
+export type ReactionObservationData = ReactionObservation.AsObject;
+export type ReactionOutcomeData = ReactionOutcome.AsObject;
+export type ReactionProvenanceData = ReactionProvenance.AsObject;
+export type ReactionSetupData = ReactionSetup.AsObject;
+export type ReactionWorkupData = ReactionWorkup.AsObject;
+
+export interface SearchResult {
+ reaction_id: string;
+ // The backend tags each result with the parent dataset id; ReactionCard
+ // renders a dataset link from it.
+ dataset_id?: string;
+ proto: string;
+ data: ReactionData;
+}
+
+export interface Dataset {
+ dataset_id: string;
+ name?: string;
+ description?: string;
+ num_reactions: number;
+}
diff --git a/app/src/utils/amount.js b/app/src/utils/amount.js
deleted file mode 100644
index 62a59ab1..00000000
--- a/app/src/utils/amount.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Copyright 2023 Open Reaction Database Project Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import reaction_pb from 'ord-schema';
-
-export const amountObj = amount => {
- if (!amount) return {};
- let units = {};
- let unitCategory = '';
- // determine unit type
- if (amount.moles) {
- units = reaction_pb.Moles.MolesUnit;
- unitCategory = 'moles';
- } else if (amount.volume) {
- units = reaction_pb.Volume.VolumeUnit;
- unitCategory = 'volume';
- } else if (amount.mass) {
- units = reaction_pb.Mass.MassUnit;
- unitCategory = 'mass';
- } else if (amount.unmeasured) {
- return { unitAmount: '', unitCategory: 'unmeasured' };
- }
- const unitVal = amount[unitCategory].units;
- const amountVal = amount[unitCategory].value;
- const precision = amount[unitCategory].precision;
- return {
- unitAmount: amountVal,
- unitType: Object.keys(units).find(key => units[key] == unitVal),
- unitCategory: unitCategory,
- };
-};
-
-export const amountStr = amountObj => {
- // takes an amountObj from above amountObj function
- if (!amountObj.unitAmount || !amountObj.unitType) return '';
- return `${Math.round(amountObj.unitAmount * 1000) / 1000} ${amountObj.unitType.toLowerCase()}`;
-};
diff --git a/app/src/utils/amount.ts b/app/src/utils/amount.ts
new file mode 100644
index 00000000..05510558
--- /dev/null
+++ b/app/src/utils/amount.ts
@@ -0,0 +1,66 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import reaction_pb from 'ord-schema';
+import type { Amount } from 'ord-schema/proto/reaction_pb';
+import { enumName } from './enum';
+
+export type AmountCategory = 'moles' | 'volume' | 'mass' | 'unmeasured' | '';
+
+export interface AmountObj {
+ unitAmount?: number;
+ unitType?: string;
+ unitCategory: AmountCategory;
+}
+
+/**
+ * Normalize a Compound.amount oneof (Amount.AsObject) into a flat
+ * { unitAmount, unitType, unitCategory } triple so render code doesn't
+ * have to branch on which oneof field is populated.
+ */
+export const amountObj = (amount: Amount.AsObject | undefined): AmountObj => {
+ if (!amount) return { unitCategory: '' };
+ if (amount.moles) {
+ return {
+ unitAmount: amount.moles.value,
+ unitType: enumName(reaction_pb.Moles.MolesUnit, amount.moles.units),
+ unitCategory: 'moles',
+ };
+ }
+ if (amount.volume) {
+ return {
+ unitAmount: amount.volume.value,
+ unitType: enumName(reaction_pb.Volume.VolumeUnit, amount.volume.units),
+ unitCategory: 'volume',
+ };
+ }
+ if (amount.mass) {
+ return {
+ unitAmount: amount.mass.value,
+ unitType: enumName(reaction_pb.Mass.MassUnit, amount.mass.units),
+ unitCategory: 'mass',
+ };
+ }
+ if (amount.unmeasured) {
+ return { unitCategory: 'unmeasured' };
+ }
+ return { unitCategory: '' };
+};
+
+export const amountStr = (obj: AmountObj): string => {
+ if (obj.unitAmount === undefined || !obj.unitType) return '';
+ return `${Math.round(obj.unitAmount * 1000) / 1000} ${obj.unitType.toLowerCase()}`;
+};
diff --git a/app/src/utils/base64.js b/app/src/utils/base64.ts
similarity index 71%
rename from app/src/utils/base64.js
rename to app/src/utils/base64.ts
index 44910974..e6825f8a 100644
--- a/app/src/utils/base64.js
+++ b/app/src/utils/base64.ts
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-import { Buffer } from 'buffer';
-
-export default base64String => {
+export const base64ToBytes = (base64String: string): ArrayBuffer => {
// converts a base64 encoded string to Uint8Array so it can be parsed by
// ord-schema js wrapper
- const buffer = Buffer.from(base64String, 'base64');
- // See https://stackoverflow.com/a/12101012.
- const arrayBuffer = new ArrayBuffer(buffer.length);
+ const binaryString = window.atob(base64String);
+ const length = binaryString.length;
+ const arrayBuffer = new ArrayBuffer(length);
const view = new Uint8Array(arrayBuffer);
- for (let i = 0; i < buffer.length; ++i) {
- view[i] = buffer[i];
+
+ for (let i = 0; i < length; i++) {
+ view[i] = binaryString.charCodeAt(i);
}
+
return arrayBuffer;
};
+
+export default base64ToBytes;
diff --git a/app/src/utils/conditions.js b/app/src/utils/conditions.js
deleted file mode 100644
index 89c43a6c..00000000
--- a/app/src/utils/conditions.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Copyright 2023 Open Reaction Database Project Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import reaction_pb from 'ord-schema';
-
-export default {
- tempType(tempControlType) {
- const controlTypes = reaction_pb.TemperatureConditions.TemperatureControl.TemperatureControlType;
- return Object.keys(controlTypes).find(key => controlTypes[key] == tempControlType);
- },
- tempSetPoint(setpoint) {
- if (!setpoint) return 'None';
- const tempUnits = reaction_pb.Temperature.TemperatureUnit;
- const tempUnit = Object.keys(tempUnits).find(key => tempUnits[key] == setpoint.units);
- return `${setpoint.value} (± ${setpoint.precision}) °${tempUnit.charAt(0)}`;
- },
- pressureType(pressControlType) {
- const controlTypes = reaction_pb.PressureConditions.PressureControl.PressureControlType;
- return Object.keys(controlTypes).find(key => controlTypes[key] == pressControlType);
- },
- pressureSetPoint(setpoint) {
- if (!setpoint) return 'None';
- const pressureUnits = reaction_pb.Pressure.PressureUnit;
- const pressureUnit = Object.keys(pressureUnits).find(key => pressureUnits[key] == setpoint.units);
- return `${setpoint.value} (± ${setpoint.precision}) ${pressureUnit.toLowerCase()}`;
- },
- pressureAtmo(atmo) {
- const atmoTypes = reaction_pb.PressureConditions.Atmosphere.AtmosphereType;
- const atmoType = Object.keys(atmoTypes).find(key => atmoTypes[key] == atmo?.type);
- return `${atmoType}${atmo?.details ? `, ${atmo.details}` : ''}`;
- },
- stirType(stirType) {
- const stirTypes = reaction_pb.StirringConditions.StirringMethodType;
- return Object.keys(stirTypes).find(key => stirTypes[key] == stirType);
- },
- stirRate(stirRate) {
- const stirRates = reaction_pb.StirringConditions.StirringRate;
- return Object.keys(stirRates).find(key => stirRates[key] == stirRate);
- },
- illumType(illum) {
- const illumTypes = reaction_pb.IlluminationConditions.IlluminationType;
- const illumType = Object.keys(illumTypes).find(key => illumTypes[key] == illum.type);
- return `${illumType}${illum.details ? `: ${illum.details}` : ''}`;
- },
-};
diff --git a/app/src/utils/conditions.ts b/app/src/utils/conditions.ts
new file mode 100644
index 00000000..481d2444
--- /dev/null
+++ b/app/src/utils/conditions.ts
@@ -0,0 +1,87 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import reaction_pb from 'ord-schema';
+import type {
+ ElectrochemistryConditions,
+ FlowConditions,
+ IlluminationConditions,
+ Length,
+ PressureConditions,
+ StirringConditions,
+ Temperature,
+ Pressure,
+} from 'ord-schema/proto/reaction_pb';
+import { enumName } from './enum';
+
+export const tempType = (type: number | undefined): string =>
+ enumName(reaction_pb.TemperatureConditions.TemperatureControl.TemperatureControlType, type) ?? '';
+
+export const tempSetPoint = (setpoint: Temperature.AsObject | undefined): string => {
+ if (!setpoint) return 'None';
+ const unit = enumName(reaction_pb.Temperature.TemperatureUnit, setpoint.units);
+ const precision = setpoint.precision ? ` (± ${setpoint.precision})` : '';
+ return `${setpoint.value}${precision} °${unit ? unit.charAt(0) : ''}`;
+};
+
+export const pressureType = (type: number | undefined): string =>
+ enumName(reaction_pb.PressureConditions.PressureControl.PressureControlType, type) ?? '';
+
+export const pressureSetPoint = (setpoint: Pressure.AsObject | undefined): string => {
+ if (!setpoint) return 'None';
+ const unit = enumName(reaction_pb.Pressure.PressureUnit, setpoint.units);
+ const precision = setpoint.precision ? ` (± ${setpoint.precision})` : '';
+ return `${setpoint.value}${precision} ${unit ? unit.toLowerCase() : ''}`;
+};
+
+export const pressureAtmo = (atmo: PressureConditions.Atmosphere.AsObject | undefined): string => {
+ const type = enumName(reaction_pb.PressureConditions.Atmosphere.AtmosphereType, atmo?.type);
+ return `${type ?? ''}${atmo?.details ? `, ${atmo.details}` : ''}`;
+};
+
+export const stirType = (type: number | undefined): string =>
+ enumName(reaction_pb.StirringConditions.StirringMethodType, type) ?? '';
+
+/**
+ * The Vue util mistakenly passed the whole StirringRate object and compared it
+ * to numeric enum values, so "Rate" always rendered as undefined. Take the
+ * type field explicitly.
+ */
+export const stirRate = (rate: StirringConditions.StirringRate.AsObject | undefined): string =>
+ enumName(reaction_pb.StirringConditions.StirringRate.StirringRateType, rate?.type) ?? '';
+
+export const illumType = (illum: IlluminationConditions.AsObject | undefined): string => {
+ if (!illum) return '';
+ const type = enumName(reaction_pb.IlluminationConditions.IlluminationType, illum.type);
+ return `${type ?? ''}${illum.details ? `: ${illum.details}` : ''}`;
+};
+
+/**
+ * Format a Length.AsObject like "5 mm". Returns `undefined` when there's
+ * nothing to show. The Vue ConditionsView used to render the whole Length
+ * object, which serialized as "[object Object]".
+ */
+export const lengthStr = (length: Length.AsObject | undefined): string | undefined => {
+ if (!length) return undefined;
+ const unit = enumName(reaction_pb.Length.LengthUnit, length.units);
+ return `${length.value}${unit ? ` ${unit.toLowerCase()}` : ''}`;
+};
+
+export const electrochemType = (type: ElectrochemistryConditions.AsObject['type'] | undefined): string =>
+ enumName(reaction_pb.ElectrochemistryConditions.ElectrochemistryType, type) ?? '';
+
+export const flowType = (type: FlowConditions.AsObject['type'] | undefined): string =>
+ enumName(reaction_pb.FlowConditions.FlowType, type) ?? '';
diff --git a/app/src/utils/enum.ts b/app/src/utils/enum.ts
new file mode 100644
index 00000000..4f4baa92
--- /dev/null
+++ b/app/src/utils/enum.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Reverse-lookup the name of a protobuf enum map (e.g.
+ * `ReactionSetup.ReactionEnvironment.ReactionEnvironmentType`) for a given numeric value.
+ *
+ * ord-schema generates these as TS interfaces (`{ UNSPECIFIED: 0; CUSTOM: 1; ... }`)
+ * rather than indexable records, so this helper takes `unknown` for the map and
+ * narrows internally rather than asking callers to add unsafe casts.
+ */
+export function enumName(enumMap: unknown, value: number | undefined): string | undefined {
+ if (value === undefined || enumMap === null || typeof enumMap !== 'object') return undefined;
+ for (const [key, mapped] of Object.entries(enumMap as Record)) {
+ if (mapped === value) return key;
+ }
+ return undefined;
+}
diff --git a/app/src/utils/outcomes.ts b/app/src/utils/outcomes.ts
new file mode 100644
index 00000000..e39b6af6
--- /dev/null
+++ b/app/src/utils/outcomes.ts
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import reaction_pb from 'ord-schema';
+import type { Percentage, Time } from 'ord-schema/proto/reaction_pb';
+import { enumName } from './enum';
+
+/**
+ * Format a Time.AsObject as " (s)" matching the Vue port's
+ * `outcomesUtil.formattedTime`. Returns null when there's nothing to show.
+ */
+export const formattedTime = (time: Time.AsObject | undefined): string | null => {
+ if (!time) return null;
+ const type = enumName(reaction_pb.Time.TimeUnit, time.units);
+ if (!type) return null;
+ // UNSPECIFIED has enum value 0; the Vue util only pluralizes the others.
+ const pluralized = time.units !== 0 ? '(s)' : '';
+ return `${time.value} ${type.toLowerCase()}${pluralized}`;
+};
+
+/**
+ * Format a Percentage.AsObject as "X%" or "X% ± Y", rounded to one decimal.
+ * Used by both ReactionCard yield/conversion and OutcomesView so the two
+ * call sites stay in sync.
+ */
+export const formatPercentage = (percentage: Percentage.AsObject | undefined): string => {
+ if (!percentage) return '';
+ const rounded = Math.round(percentage.value * 10) / 10;
+ // Percentage.precision defaults to 0 in proto3; treat 0 as "no precision
+ // recorded" rather than "± 0", matching the Vue OutcomesView's
+ // `isNaN(precision)` guard.
+ const precision =
+ Number.isFinite(percentage.precision) && percentage.precision !== 0 ? ` ± ${percentage.precision}` : '';
+ return `${rounded}%${precision}`;
+};
diff --git a/app/src/views/About.scss b/app/src/views/About.scss
new file mode 100644
index 00000000..e06559ee
--- /dev/null
+++ b/app/src/views/About.scss
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../styles/vars' as vars;
+
+.about {
+ margin-top: 2rem;
+ background-color: white;
+ padding: 0 2rem;
+ border-radius: 0.5rem;
+
+ &__section {
+ margin-top: 3rem;
+ }
+
+ &__section-header {
+ border-top: 1px solid #dee2e6;
+ padding-top: 1rem;
+ }
+
+ &__section-title {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 500;
+ }
+
+ &__section-content {
+ padding: 1.5rem;
+ }
+
+ &__text {
+ text-align: justify;
+ text-justify: inter-word;
+ margin-bottom: 1rem;
+ line-height: 1.6;
+ }
+
+ &__list {
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+ }
+
+ &__list-item {
+ margin-bottom: 0.5rem;
+ line-height: 1.5;
+ }
+
+ &__subsection-title {
+ font-size: 1.25rem;
+ font-weight: 500;
+ margin-bottom: 1rem;
+ margin-top: 1.5rem;
+ }
+
+ &__link {
+ color: vars.$linkblue;
+ text-decoration: none;
+
+ &:hover {
+ color: vars.$hoverblue;
+ text-decoration: underline;
+ }
+ }
+
+ &__leadership-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+ margin-bottom: 1.5rem;
+
+ @media (max-width: 768px) {
+ grid-template-columns: 1fr;
+ gap: 1rem;
+ }
+ }
+
+ &__leadership-column {
+ margin: 0;
+ }
+}
diff --git a/app/src/views/About.tsx b/app/src/views/About.tsx
new file mode 100644
index 00000000..dcb0fdf4
--- /dev/null
+++ b/app/src/views/About.tsx
@@ -0,0 +1,220 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import './About.scss';
+
+const About: React.FC = () => {
+ return (
+
+
+
+
About
+
+
+
+ The Open Reaction Database (ORD) is an open-access schema and infrastructure for structuring and sharing
+ organic reaction data, including a centralized data repository. The ORD schema supports conventional and
+ emerging technologies, from benchtop reactions to automated high-throughput experiments and flow chemistry.
+ Our vision is that a consistent data representation and infrastructure to support data sharing will enable
+ downstream applications that will greatly improve the state of the art with respect to computer-aided
+ synthesis planning, reaction prediction, and other predictive chemistry tasks.
+
+
+ Since our initial meeting in October 2019, the database has grown to include more than 2M reactions
+ (including a large dataset of reactions extracted from USPTO sources) and received contributions from
+ academic and industrial users, both from published and unpublished work. Some of our current efforts
+ include:
+
+
+
+ Improving user interfaces and providing support to contributors on GitHub and via email.
+
+
+ Working with companies to incorporate the ORD schema into their reaction informatics infrastructure,
+ including the development of "translators" between the ORD schema and electronic lab notebooks (ELNs).
+
+
+ Engaging with journals and other stakeholders to drive adoption of the ORD schema as a{' '}
+
+ FAIR
+ {' '}
+ data structure for sharing reaction data across academia, government, and industry.
+
+
+
+
+ Ben Deadman
+ {' '}
+ is our Reaction Data Evangelist and ORD Program Manager. Please reach out to him at{' '}
+
+ help@open‑reaction‑database.org
+ {' '}
+ for help preparing a contribution or to discuss using the ORD in your company or lab.
+
+
+
+
+
+
+
Publications and Media
+
+
+
Journal Articles
+
+
+ Kearnes SM, Maser MR, Wleklinski M, Kast A, Doyle AG, Dreher SD, Hawkins JM, Jensen KF, Coley CW. The Open
+ Reaction Database. J Am Chem Soc 2021, 143(45), 18820-18826. (
+
+ JACS
+
+ )
+
+
+ Mercado R, Kearnes SM, Coley C. Data Sharing in Chemistry: Lessons Learned and a Case for Mandating
+ Structured Reaction Data. J Chem Inf Model 2023, 63(14), 4253-4265. (
+
+ JCIM
+
+ )
+
+
+
News
+
+
+ A new database for machine-learning research (
+
+ C&EN
+
+ , 22 November 2021)
+
+
+ Yield-predicting AI needs chemists to stop ignoring failed experiments (
+
+ Chemistry World
+
+ , 12 May 2022)
+
+
+ Chemists debate machine learning's future in synthesis planning and ask for open data (
+
+ C&EN
+
+ , 18 May 2022)
+
+
+ For chemists, the AI revolution has yet to happen (
+
+ Nature
+
+ , 17 May 2023)
+
+
+
+
+
+
+
+
Leadership
+
+
+
Governing Committee
+
+
+ Connor Coley (MIT, C-CAS)
+ Abby Doyle (UCLA, C-CAS)
+ Spencer Dreher (Merck)
+
+
+ Joel Hawkins (Pfizer)
+ Klavs Jensen (MIT)
+ Steven Kearnes (Relay)
+
+
+
Advisory Board
+
+
+ Alán Aspuru-Guzik (Toronto, MADNESS)
+ Timothy Cernak (Michigan, Entos)
+ Lucy Colwell (Cambridge, SynTech, Google)
+ Werngard Czechtizky (AstraZeneca)
+ JW Feng
+ Matthew Gaunt (Cambridge, SynTech)
+ Alex Godfrey (NCATS Consultant)
+ Mimi Hii (Imperial, ROAR)
+ Greg Landrum (T5 Informatics)
+
+
+ Fabio Lima (Novartis)
+ Christos Nicolaou (Recursion)
+ Sarah Reisman (Caltech, C-CAS)
+ Francesco Rianjongdee (GSK)
+ Marwin Segler (Microsoft)
+ Matthew Sigman (Utah, C-CAS)
+ Jay Stevens (BMS)
+ Sarah Trice (XtalPi)
+ Huimin Zhao (UIUC, MMLI)
+
+
+
+
+
+
+
+
Support
+
+
+
We gratefully acknowledge support from:
+
+ Google
+ Relay Therapeutics
+ Schmidt Futures
+ University of Notre Dame
+
+
+
+
+ );
+};
+
+export default About;
diff --git a/app/src/views/About.vue b/app/src/views/About.vue
deleted file mode 100644
index ffc849b8..00000000
--- a/app/src/views/About.vue
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-
-
-
-
-
-
-
About
-
-
-
- The Open Reaction Database (ORD) is an open-access schema and infrastructure for structuring and sharing
- organic reaction data, including a centralized data repository. The ORD schema supports conventional and
- emerging technologies, from benchtop reactions to automated high-throughput experiments and flow chemistry.
- Our vision is that a consistent data representation and infrastructure to support data sharing will enable
- downstream applications that will greatly improve the state of the art with respect to computer-aided
- synthesis planning, reaction prediction, and other predictive chemistry tasks.
-
-
- Since our initial meeting in October 2019, the database has grown to include more than 2M reactions (including
- a large dataset of reactions extracted from USPTO sources) and received contributions from academic and
- industrial users, both from published and unpublished work. Some of our current efforts include:
-
-
- Improving user interfaces and providing support to contributors on GitHub and via email.
-
- Working with companies to incorporate the ORD schema into their reaction informatics infrastructure,
- including the development of "translators" between the ORD schema and electronic lab notebooks (ELNs).
-
-
- Engaging with journals and other stakeholders to drive adoption of the ORD schema as a
- FAIR data structure for sharing reaction data across
- academia, government, and industry.
-
-
-
- Ben Deadman
- is our Reaction Data Evangelist and ORD Program Manager. Please reach out to him at
- help@open‑reaction‑database.org
- for help preparing a contribution or to discuss using the ORD in your company or lab.
-
-
-
-
-
-
Publications and Media
-
-
-
Journal Articles
-
-
- Kearnes SM, Maser MR, Wleklinski M, Kast A, Doyle AG, Dreher SD, Hawkins JM, Jensen KF, Coley CW. The Open
- Reaction Database. J Am Chem Soc 2021, 143(45), 18820-18826. (JACS )
-
-
- Mercado R, Kearnes SM, Coley C. Data Sharing in Chemistry: Lessons Learned and a Case for Mandating
- Structured Reaction Data. J Chem Inf Model 2023, 63(14), 4253-4265. (JCIM )
-
-
-
News
-
-
- A new database for machine-learning research (C&EN , 22 November 2021)
-
-
- Yield-predicting AI needs chemists to stop ignoring failed experiments (Chemistry World , 12 May 2022)
-
-
- Chemists debate machine learning's future in synthesis planning and ask for open data (C&EN , 18 May 2022)
-
-
- For chemists, the AI revolution has yet to happen (Nature , 17 May 2023)
-
-
-
-
-
-
-
Leadership
-
-
-
Governing Committee
-
-
-
- Connor Coley (MIT, C-CAS)
- Abby Doyle (UCLA, C-CAS)
- Spencer Dreher (Merck)
-
-
-
-
- Joel Hawkins (Pfizer)
- Klavs Jensen (MIT)
- Steven Kearnes (Relay)
-
-
-
-
Advisory Board
-
-
-
- Alán Aspuru-Guzik (Toronto, MADNESS)
- Timothy Cernak (Michigan, Entos)
- Lucy Colwell (Cambridge, SynTech, Google)
- Werngard Czechtizky (AstraZeneca)
- JW Feng
- Matthew Gaunt (Cambridge, SynTech)
- Alex Godfrey (NCATS Consultant)
- Mimi Hii (Imperial, ROAR)
- Greg Landrum (T5 Informatics)
-
-
-
-
- Fabio Lima (Novartis)
- Christos Nicolaou (Recursion)
- Sarah Reisman (Caltech, C-CAS)
- Francesco Rianjongdee (GSK)
- Marwin Segler (Microsoft)
- Matthew Sigman (Utah, C-CAS)
- Jay Stevens (BMS)
- Sarah Trice (XtalPi)
- Huimin Zhao (UIUC, MMLI)
-
-
-
-
-
-
-
-
Support
-
-
-
We gratefully acknowledge support from:
-
- Google
- Relay Therapeutics
- Schmidt Futures
- University of Notre Dame
-
-
-
-
-
-
-
diff --git a/app/src/styles/table.sass b/app/src/views/Home.scss
similarity index 63%
rename from app/src/styles/table.sass
rename to app/src/views/Home.scss
index dafdb646..a7c52524 100644
--- a/app/src/styles/table.sass
+++ b/app/src/views/Home.scss
@@ -14,21 +14,19 @@
* limitations under the License.
*/
-@use '@/styles/vars' as *
+@use '../styles/vars.scss' as vars;
-.content
- .table-container
- background-color: white
- width: 90%
- margin: auto
- border-radius: 0.5rem
- display: grid
- overflow: hidden
- display: grid
- .column
- border-top: 1px solid $medgrey
- padding: 0.5rem
- &.label
- border-top: none
- font-weight: 700
- font-size: 1.25rem
\ No newline at end of file
+.home {
+ margin-top: 20vh;
+
+ &__image-container {
+ width: 90%;
+ margin: auto;
+ max-width: 800px;
+ }
+
+ &__logo {
+ width: 100%;
+ height: auto;
+ }
+}
diff --git a/app/src/views/Home.tsx b/app/src/views/Home.tsx
new file mode 100644
index 00000000..951c871f
--- /dev/null
+++ b/app/src/views/Home.tsx
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import './Home.scss';
+
+const Home: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/app/src/views/Home.vue b/app/src/views/Home.vue
deleted file mode 100644
index aaf2e5f8..00000000
--- a/app/src/views/Home.vue
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-.home
- .home-img
- img( alt="Ord logo" src="https://raw.githubusercontent.com/Open-Reaction-Database/ord-schema/main/logos/logo.svg")
-
-
-
diff --git a/app/src/styles/transition.sass b/app/src/views/browse/MainBrowse.scss
similarity index 61%
rename from app/src/styles/transition.sass
rename to app/src/views/browse/MainBrowse.scss
index 99c516e3..0844287e 100644
--- a/app/src/styles/transition.sass
+++ b/app/src/views/browse/MainBrowse.scss
@@ -14,23 +14,17 @@
* limitations under the License.
*/
-.expand-enter-active, .expand-leave-active
- transition: .5s ease
- max-height: 400px
- opacity: 1
+@use '../../styles/vars.scss' as vars;
+@use '../../styles/table.scss' as table;
-.expand-enter-from, .expand-leave-to
- max-height: 0
- overflow: hidden
- opacity: 0
+#browse-main {
+ padding: 1rem 0;
-.expand-enter-to, .expand-leave-from
- max-height: 400px
- overflow: hidden
- opacity: 1
+ .table-container {
+ grid-template-columns: 1fr 1fr 1fr auto;
+ }
-.fade-enter-active, .fade-leave-active
- transition: opacity .25s
-
-.fade-enter-from, .fade-leave-to
- opacity: 0
\ No newline at end of file
+ .loading {
+ margin-top: 30vh;
+ }
+}
diff --git a/app/src/views/browse/MainBrowse.tsx b/app/src/views/browse/MainBrowse.tsx
new file mode 100644
index 00000000..06a8f0c2
--- /dev/null
+++ b/app/src/views/browse/MainBrowse.tsx
@@ -0,0 +1,101 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import EntityTable from '../../components/EntityTable';
+import LoadingSpinner from '../../components/LoadingSpinner';
+import './MainBrowse.scss';
+
+interface Dataset {
+ dataset_id: string;
+ name: string;
+ description?: string;
+ num_reactions: number;
+}
+
+const MainBrowse: React.FC = () => {
+ const [loading, setLoading] = useState(true);
+ const [tableData, setTableData] = useState([]);
+
+ useEffect(() => {
+ fetch('/api/datasets', { method: 'GET' })
+ .then(response => response.json())
+ .then((data: Dataset[]) => {
+ setTableData(data);
+ setLoading(false);
+ })
+ .catch(error => {
+ console.error('Error fetching datasets:', error);
+ setLoading(false);
+ });
+ }, []);
+
+ const truncateDescription = (description?: string) => {
+ if (!description) return '';
+ return description.length > 75 ? description.substr(0, 75) + '...' : description;
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!tableData.length) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ {(entities: Dataset[]) => (
+
+
Dataset ID
+
Name
+
Description
+
Size
+ {entities.map(row => (
+
+
+ {row.dataset_id}
+
+ {row.name}
+ {truncateDescription(row.description)}
+ {row.num_reactions}
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+export default MainBrowse;
diff --git a/app/src/views/browse/MainBrowse.vue b/app/src/views/browse/MainBrowse.vue
deleted file mode 100644
index 3f792b7c..00000000
--- a/app/src/views/browse/MainBrowse.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-#browse-main
- EntityTable(
- :tableData='tableData'
- title="",
- v-slot='{ entities }'
- v-if='tableData.length'
- )
- .table-container
- .column.label Dataset ID
- .column.label Name
- .column.label Description
- .column.label Size
- template(
- v-for='(row, idx) in entities'
- )
- .column
- router-link(:to='{ name: "dataset-view", params: {datasetId: row.dataset_id}}') {{row.dataset_id}}
- .column {{row.name}}
- .column {{row.description?.length > 75 ? row.description.substr(0,75)+"..." : row.description}}
- .column {{row.num_reactions}}
- .loading(v-else)
- LoadingSpinner
-
-
-
diff --git a/app/src/main.js b/app/src/views/browse/selected-set/MainSelectedSet.scss
similarity index 51%
rename from app/src/main.js
rename to app/src/views/browse/selected-set/MainSelectedSet.scss
index 126ccffc..d1bb0606 100644
--- a/app/src/main.js
+++ b/app/src/views/browse/selected-set/MainSelectedSet.scss
@@ -14,28 +14,42 @@
* limitations under the License.
*/
-import { createApp } from 'vue';
-import { createStore } from 'vuex';
-
-import App from './App.vue';
-import router from './router';
-
-const store = createStore({
- state() {
- return { storedSet: null, downloadFileType: null };
- },
- mutations: {
- setStoredSet(state, data) {
- state.storedSet = data;
- },
- setDownloadFileType(state, data) {
- state.downloadFileType = data;
- },
- },
- actions: {},
-});
-
-const app = createApp(App);
-app.use(router);
-app.use(store);
-app.mount('#app');
+@use '../../../styles/vars.scss' as vars;
+
+#selected-set-main {
+ padding: 2rem;
+
+ .header {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ margin-bottom: 1rem;
+
+ .title {
+ font-size: 2rem;
+ font-weight: 700;
+ }
+
+ .action-button-holder {
+ display: flex;
+ align-items: end;
+ column-gap: 0.5rem;
+ }
+ }
+
+ .no-results {
+ text-align: center;
+ padding: 4rem 2rem;
+
+ .title {
+ font-size: 1.5rem;
+ color: #666;
+ }
+ }
+
+ .loading {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 50vh;
+ }
+}
diff --git a/app/src/views/browse/selected-set/MainSelectedSet.tsx b/app/src/views/browse/selected-set/MainSelectedSet.tsx
new file mode 100644
index 00000000..c7319679
--- /dev/null
+++ b/app/src/views/browse/selected-set/MainSelectedSet.tsx
@@ -0,0 +1,134 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import reaction_pb from 'ord-schema';
+import LoadingSpinner from '../../../components/LoadingSpinner';
+import ReactionCard from '../../../components/ReactionCard';
+import DownloadResults from '../../../components/DownloadResults';
+import CopyButton from '../../../components/CopyButton';
+import { base64ToBytes } from '../../../utils/base64';
+import type { SearchResult } from '../../../types/search';
+import './MainSelectedSet.scss';
+
+const MainSelectedSet: React.FC = () => {
+ const [searchParams] = useSearchParams();
+ const [reactions, setReactions] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [showDownloadResults, setShowDownloadResults] = useState(false);
+
+ const reactionIds = useMemo(() => searchParams.getAll('reaction_id'), [searchParams]);
+ const reactionIdsKey = reactionIds.join(',');
+ const fullUrl = window.location.href;
+
+ const getSelectedReactions = useCallback(async () => {
+ setLoading(true);
+ try {
+ const response = await fetch('/api/reactions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ reaction_ids: reactionIds }),
+ });
+
+ if (!response.ok) throw new Error('Failed to fetch reactions');
+ const fetched = (await response.json()) as Array>;
+
+ const decoded: SearchResult[] = fetched.map(r => ({
+ ...r,
+ data: reaction_pb.Reaction.deserializeBinary(new Uint8Array(base64ToBytes(r.proto))).toObject(),
+ }));
+
+ setReactions(decoded);
+ } catch (error) {
+ console.error('Error fetching reactions:', error);
+ setReactions([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [reactionIdsKey]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ useEffect(() => {
+ if (reactionIds.length > 0) {
+ getSelectedReactions();
+ } else {
+ setLoading(false);
+ }
+ }, [reactionIdsKey, getSelectedReactions]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!reactions.length) {
+ return (
+
+
+
+
There was an issue fetching your selected reactions.
+
+
+ );
+ }
+
+ return (
+
+
+
Reaction Set
+
+
+ setShowDownloadResults(true)}
+ >
+ Download Reaction Set
+
+
+
+
+
+ {reactions.map(reaction => (
+
+ ))}
+
+
+
setShowDownloadResults(false)}
+ />
+
+ );
+};
+
+export default MainSelectedSet;
diff --git a/app/src/views/browse/selected-set/MainSelectedSet.vue b/app/src/views/browse/selected-set/MainSelectedSet.vue
deleted file mode 100644
index 573ffec9..00000000
--- a/app/src/views/browse/selected-set/MainSelectedSet.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-
-
-
-
-
-#selected-set-main
- .header
- .title Reaction Set
- .action-button-holder
- CopyButton(
- :textToCopy='fullUrl'
- icon='share'
- buttonText='Shareable Link'
- )
- button(
- :disabled='!reactionIds.length'
- @click='showDownloadResults=true'
- ) Download Reaction Set
- .selected-set(v-if='!loading && reactions.length')
- ReactionCard(
- v-for='reaction in reactions'
- :reaction='reaction'
- :isSelectable='false'
- :isSelected='false'
- )
- .no-results(v-else-if='!loading && !reactions?.length')
- .title There was an issue fetching your selected reactions.
- .loading(v-else)
- LoadingSpinner
- DownloadResults(
- :reactionIds='reactionIds'
- :showDownloadResults='showDownloadResults'
- @hideDownloadResults='showDownloadResults=false'
- )
-
-
-
diff --git a/app/src/views/contribute/DatasetsView.vue b/app/src/views/contribute/DatasetsView.vue
deleted file mode 100644
index 1eff2a9d..00000000
--- a/app/src/views/contribute/DatasetsView.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-.create
- .subtitle Create dataset:
- .file-picker
- .input
- label(for='new-dataset-name') Dataset name:
- input#new-dataset-name(
- type='text'
- v-model='newDatasetName'
- )
- .submit
- button#create-dataset(@click='createDataset') Create Dataset
- .copy Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt eros eget magna venenatis, nec volutpat justo ornare. Praesent mauris enim, dignissim nec posuere ut, mollis at lacus. Phasellus ut ultricies arcu, ultrices efficitur enim. Donec pharetra turpis nulla, id elementum felis fermentum sit amet. In id imperdiet sapien. Quisque vulputate odio eget ex aliquet aliquam. Vestibulum a metus magna.
-
-
-
diff --git a/app/src/views/contribute/EnumerateView.vue b/app/src/views/contribute/EnumerateView.vue
deleted file mode 100644
index 0ee5b65f..00000000
--- a/app/src/views/contribute/EnumerateView.vue
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
-
-
-
-.enumerate
- .subtitle Upload files for enumeration:
- .file-picker
- .input
- label(for='template') Template filename:
- input#template(
- type='file'
- accept='.pbtxt'
- v-on:change='(e) => setFile(e,"template")'
- )
- .input
- label(for='spreadsheet') Spreadsheet filename:
- input#spreadsheet(
- type='file'
- accept='.csv,.xls,.xlsx'
- v-on:change='(e) => setFile(e,"spreadsheet")'
- )
- .submit
- button#enumerate_submit(@click='submitEnumerate') Submit Enumeration Upload
- .copy You can use a Reaction template to enumerate a Dataset based on a spreadsheet of values. For more information, see the
- a(
- href='https://docs.open-reaction-database.org/en/latest/guides/templates.html'
- target="_blank"
- ) documentation
- | .
- .copy
- b NOTE:
- | Large dataset enumerations (thousands of reactions) may result in a browser timeout. If this happens, please send an email to
- a(
- href='mailto:help@open-reaction-database.org'
- target="_blank"
- ) help@open-reaction-database.org
- | and attach your template and spreadsheet files. Alternatively, you may use the
- a(
- href='https://github.com/Open-Reaction-Database/ord-schema/blob/main/ord_schema/scripts/enumerate_dataset.py'
- target="_blank"
- ) programmatic interface
- | to enumerate the dataset locally.
-
-
-
-
diff --git a/app/src/views/contribute/MainContribute.vue b/app/src/views/contribute/MainContribute.vue
deleted file mode 100644
index 7d0c1138..00000000
--- a/app/src/views/contribute/MainContribute.vue
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
-.main-contribute
- transition(name="fade")
- .loading(v-if='loading')
- LoadingSpinner
- transition(name="fade")
- .contribute-menu(v-if='!loading')
- .title {{activeTab}}
- .contribute-container
- .tabs
- .tab(
- v-for='tab in tabs'
- @click='activeTab = tab'
- :class='activeTab == tab ? "selected" : ""'
- ) {{tab}}
- transition(name="fade")
- .get-started(v-if='activeTab == "Get Started"')
- .copy Check out these
- a(href="https://www.youtube.com/playlist?list=PLyoEVAlMb276aRRa4xLNRAzbMPRlNb7VI") tutorial videos
- | for a guide on how to use the ORD Editor.
- .tutorial-videos
- .video
- .subtitle Using the interactive web editor (updated 1/2021)
- iframe(
- width='560'
- height='315'
- src='https://www.youtube.com/embed/5J-2j8aBXMo'
- title='YouTube video player'
- frameborder='0'
- allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
- allowfullscreen
- )
- .video
- .subtitle Submitting your first Dataset
- iframe(
- width='560'
- height='315'
- src='https://www.youtube.com/embed/sAdSKKdO9Gs'
- title='YouTube video player'
- frameborder='0'
- allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
- allowfullscreen
- )
- .copy Please send any questions, comments, or issues to
- a(
- href='https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=help@open-reaction-database.org'
- target="_blank"
- ) help@open-reaction-database.org
- | or create a new issue on the
- a(
- href='https://github.com/Open-Reaction-Database/ord-interface/issues'
- target="_blank"
- ) ord-interface GitHub repository
- | .
- transition(name="fade")
- Datasets(v-if='activeTab == "Datasets"')
- transition(name="fade")
- Upload(v-if='activeTab == "Upload"')
- transition(name="fade")
- Enumerate(v-if='activeTab == "Enumerate"')
-
-
-
-
diff --git a/app/src/views/contribute/UploadView.vue b/app/src/views/contribute/UploadView.vue
deleted file mode 100644
index 017e22f9..00000000
--- a/app/src/views/contribute/UploadView.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
-.upload
- .subtitle Upload dataset file:
- .file-picker
- .input
- label(for='upload') Dataset filename:
- input#template(
- type='file'
- accept='.pbtxt,.pb'
- v-on:change='(e) => setFile(e)'
- )
- .submit
- button#upload-submit(@click='submitUpload') Submit Upload
- .copy Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt eros eget magna venenatis, nec volutpat justo ornare. Praesent mauris enim, dignissim nec posuere ut, mollis at lacus. Phasellus ut ultricies arcu, ultrices efficitur enim. Donec pharetra turpis nulla, id elementum felis fermentum sit amet. In id imperdiet sapien. Quisque vulputate odio eget ex aliquet aliquam. Vestibulum a metus magna.
-
-
-
diff --git a/app/src/views/dataset-view/ChartView.scss b/app/src/views/dataset-view/ChartView.scss
new file mode 100644
index 00000000..42ec4936
--- /dev/null
+++ b/app/src/views/dataset-view/ChartView.scss
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// ChartView component styles
+@use '../../styles/vars.scss' as vars;
+
+.chart-view {
+ &__title-and-chart {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ &__title {
+ margin-bottom: 10px;
+ }
+
+ &__loading {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ &__tooltip {
+ position: fixed;
+ background: white;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ padding: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+ z-index: 1000;
+ pointer-events: none;
+ max-width: 300px;
+
+ pre {
+ margin: 0 0 8px 0;
+ font-family: monospace;
+ font-size: 12px;
+ }
+ }
+
+ &__svg {
+ svg {
+ width: 100%;
+ height: auto;
+ }
+ }
+
+ &__molloading {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+}
diff --git a/app/src/views/dataset-view/ChartView.tsx b/app/src/views/dataset-view/ChartView.tsx
new file mode 100644
index 00000000..1aab1b7c
--- /dev/null
+++ b/app/src/views/dataset-view/ChartView.tsx
@@ -0,0 +1,270 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useEffect, useRef, useState, useCallback } from 'react';
+import * as d3 from 'd3';
+import LoadingSpinner from '../../components/LoadingSpinner';
+import reaction_pb from 'ord-schema';
+import './ChartView.scss';
+
+interface ChartData {
+ smiles: string;
+ times_appearing: number;
+}
+
+interface ChartViewProps {
+ uniqueId: string;
+ title: string;
+ apiCall: string;
+ role: string;
+ isCollapsed?: boolean;
+}
+
+const ChartView: React.FC = ({ uniqueId, title, apiCall, role, isCollapsed = false }) => {
+ const [loading, setLoading] = useState(true);
+ const [inputsData, setInputsData] = useState([]);
+ const [showTooltip, setShowTooltip] = useState<'visible' | 'hidden'>('hidden');
+ const [currentTimesAppearing, setCurrentTimesAppearing] = useState(0);
+ const [tooltipOffsetHorizontal, setTooltipOffsetHorizontal] = useState(0);
+ const [tooltipOffsetVertical, setTooltipOffsetVertical] = useState(0);
+ const [showSmiles, setShowSmiles] = useState(false);
+ const [molHtml, setMolHtml] = useState(null);
+ const [molLoading, setMolLoading] = useState(true);
+
+ const svgRef = useRef(null);
+
+ const getMolHtml = useCallback(async (smiles: string): Promise => {
+ const compound = new reaction_pb.Compound();
+ const identifier = compound.addIdentifiers();
+ identifier.setValue(smiles);
+ identifier.setType(reaction_pb.CompoundIdentifier.CompoundIdentifierType.SMILES);
+
+ const binary = compound.serializeBinary();
+
+ const response = await fetch('/api/compound_svg', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-protobuf',
+ },
+ body: binary as BodyInit,
+ });
+
+ // Throw on non-2xx so the caller's .catch sets molHtml to null instead
+ // of feeding an HTML error page to dangerouslySetInnerHTML.
+ if (!response.ok) {
+ throw new Error(`compound_svg failed (HTTP ${response.status})`);
+ }
+ return response.json();
+ }, []);
+
+ const createChart = useCallback(
+ (data: ChartData[], width: number, height: number) => {
+ if (!svgRef.current) return;
+
+ // Clear previous chart
+ d3.select(svgRef.current).selectAll('*').remove();
+
+ const marginTop = 20;
+ const marginRight = 20;
+ const marginBottom = 30;
+ const marginLeft = 40;
+
+ // X-axis
+ const x = d3
+ .scaleBand()
+ .domain(
+ d3.groupSort(
+ data,
+ ([d]) => -d.times_appearing,
+ d => d.smiles,
+ ),
+ )
+ .range([marginLeft, width - marginRight])
+ .padding(0.1);
+
+ // Y-axis
+ const y = d3
+ .scaleLinear()
+ .domain([0, d3.max(data, d => d.times_appearing) || 0])
+ .range([height - marginBottom, marginTop]);
+
+ // SVG setup
+ const svg = d3
+ .select(svgRef.current)
+ .attr('width', width)
+ .attr('height', height)
+ .attr('viewBox', [0, 0, width, height])
+ .attr('style', 'max-width: 100%; height: auto;');
+
+ // Create bars
+ svg
+ .append('g')
+ .attr('fill', 'steelblue')
+ .selectAll('rect')
+ .data(data)
+ .join('rect')
+ .attr('x', d => x(d.smiles) || 0)
+ .attr('y', d => y(d.times_appearing))
+ .attr('height', d => y(0) - y(d.times_appearing))
+ .attr('width', x.bandwidth())
+ .attr('style', 'cursor: pointer;')
+ .on('mouseover', (event: MouseEvent, d: ChartData) => {
+ setCurrentTimesAppearing(d.times_appearing);
+ setTooltipOffsetHorizontal(event.clientX);
+ setTooltipOffsetVertical(role === 'product' ? event.clientY - 240 : event.clientY - 140);
+ setShowTooltip('visible');
+ setShowSmiles(true);
+ setMolLoading(true);
+
+ getMolHtml(d.smiles)
+ .then(result => {
+ setMolLoading(false);
+ setMolHtml(result);
+ })
+ .catch(error => {
+ console.error('Error fetching molecule SVG:', error);
+ setMolLoading(false);
+ setMolHtml(null);
+ });
+ })
+ .on('mouseout', () => {
+ setShowTooltip('hidden');
+ setMolHtml(null);
+ setShowSmiles(false);
+ setMolLoading(true);
+ });
+
+ // X-axis formatting
+ svg
+ .append('g')
+ .attr('transform', `translate(0,${height - marginBottom})`)
+ .call(
+ d3
+ .axisBottom(x)
+ .ticks(width / 80)
+ .tickSizeOuter(0)
+ .tickFormat(() => ''),
+ )
+ .call(g =>
+ g
+ .append('text')
+ .attr('x', width)
+ .attr('y', marginBottom - 4)
+ .attr('fill', 'currentColor')
+ .attr('text-anchor', 'end')
+ .text('Molecules (Hover to view) →'),
+ );
+
+ // Y-axis formatting
+ svg
+ .append('g')
+ .attr('transform', `translate(${marginLeft},0)`)
+ .call(d3.axisLeft(y).tickFormat(y => (y as number).toFixed()))
+ .call(g => g.select('.domain').remove())
+ .call(g =>
+ g
+ .append('text')
+ .attr('x', -marginLeft)
+ .attr('y', 10)
+ .attr('fill', 'currentColor')
+ .attr('text-anchor', 'start')
+ .text('↑ Frequency (no. of occurrences)'),
+ );
+ },
+ [getMolHtml, role],
+ );
+
+ const resize = useCallback(() => {
+ const width = isCollapsed ? 180 : 400;
+ const height = isCollapsed ? 180 : 400;
+ createChart(inputsData, width, height);
+ }, [inputsData, isCollapsed, createChart]);
+
+ // Fetch data on mount. The resize effect below renders the chart once
+ // inputsData populates, so isCollapsed/createChart are intentionally not
+ // deps here — including them would re-fire the fetch every collapse toggle.
+ useEffect(() => {
+ const datasetId = window.location.pathname.split('/')[2];
+
+ fetch(`/api/${apiCall}?dataset_id=${datasetId}`, { method: 'GET' })
+ .then(response => response.json())
+ .then((data: ChartData[]) => {
+ setLoading(false);
+ setInputsData(data);
+ })
+ .catch(() => {
+ setLoading(false);
+ });
+ }, [apiCall]);
+
+ // Resize chart when isCollapsed changes
+ useEffect(() => {
+ if (inputsData.length > 0) {
+ resize();
+ }
+ }, [isCollapsed, resize, inputsData.length]);
+
+ return (
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+ {showSmiles && (
+
+
Count: {currentTimesAppearing}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ChartView;
diff --git a/app/src/views/dataset-view/ChartView.vue b/app/src/views/dataset-view/ChartView.vue
deleted file mode 100644
index b4accf98..00000000
--- a/app/src/views/dataset-view/ChartView.vue
+++ /dev/null
@@ -1,357 +0,0 @@
-
-
-
-
-
- .div
- .span(:style='isCollapsed ? "font-size: 10pt; width: 150px;" : "font-size: 14pt;"') {{this.title}}
- svg(:id='uniqueId' :style='!loading ? "visibility: visible" : "visibility: hidden"')
- .loading(:style='loading ? "visibility: visible" : "visibility: hidden"')
- LoadingSpinner
- .tooltip(:style='"top: " + tooltipOffsetVertical + "px; left: " + tooltipOffsetHorizontal + "px; visibility: " + showTooltip' v-if='showSmiles')
- pre Count: {{currentTimesAppearing}}
- .svg(
- v-html='molHtml'
- )
- .molloading(:style='this.molloading ? "visibility: visible" : "visibility: hidden"')
- LoadingSpinner
-
diff --git a/app/src/views/dataset-view/MainDatasetView.scss b/app/src/views/dataset-view/MainDatasetView.scss
new file mode 100644
index 00000000..380978be
--- /dev/null
+++ b/app/src/views/dataset-view/MainDatasetView.scss
@@ -0,0 +1,182 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../../styles/vars.scss' as vars;
+
+#dataset-main {
+ padding: 2rem;
+
+ h1 {
+ margin-bottom: 2rem;
+ font-size: 2rem;
+ font-weight: 700;
+ }
+}
+
+#charts {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 2rem;
+ margin-bottom: 2rem;
+}
+
+.charts-container {
+ height: fit-content;
+ background: white;
+ border-radius: 0.5rem;
+ padding: 1.5rem;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ border: 1px solid vars.$lightgrey;
+}
+
+.charts-header {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 1rem;
+
+ #expand {
+ button {
+ background: white;
+ border: 1px solid vars.$medgrey;
+ border-radius: 0.25rem;
+ padding: 0.5rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background: vars.$lightgrey;
+ }
+
+ .material-icons {
+ font-size: 1.2rem;
+ color: vars.$darkgrey;
+ }
+ }
+ }
+}
+
+#chartsection {
+ display: flex;
+ flex-direction: column;
+}
+
+.charts-content {
+ display: block;
+
+ &.expanded {
+ display: flex;
+ justify-content: center;
+ gap: 2rem;
+ }
+}
+
+#chartsectioncharts {
+ .chart-placeholder {
+ background-color: white;
+ border: 1px solid vars.$medgrey;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ margin-bottom: 1rem;
+
+ h3 {
+ margin: 0 0 0.5rem 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ }
+
+ p {
+ margin: 0;
+ color: #666;
+ font-style: italic;
+ }
+ }
+}
+
+#datasection {
+ .h4 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+ }
+
+ > table {
+ width: 100%;
+ background-color: white;
+ border-collapse: collapse;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ margin-bottom: 2rem;
+
+ tr {
+ &:nth-child(even) {
+ background-color: #f8f9fa;
+ }
+ }
+
+ td {
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid vars.$medgrey;
+
+ &:first-child {
+ font-weight: 600;
+ background-color: vars.$lightgrey;
+ white-space: nowrap;
+ }
+
+ &:last-child {
+ width: 100%;
+ }
+ }
+ }
+
+ .search-results {
+ .results-placeholder {
+ background-color: white;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+ .result-item {
+ padding: 0.5rem 0;
+ border-bottom: 1px solid vars.$medgrey;
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+ }
+
+ .no-results {
+ text-align: center;
+ padding: 2rem;
+
+ .title {
+ font-size: 1.25rem;
+ color: #666;
+ }
+ }
+
+ .loading {
+ display: flex;
+ justify-content: center;
+ padding: 2rem;
+ }
+ }
+}
diff --git a/app/src/views/dataset-view/MainDatasetView.tsx b/app/src/views/dataset-view/MainDatasetView.tsx
new file mode 100644
index 00000000..d5f44b09
--- /dev/null
+++ b/app/src/views/dataset-view/MainDatasetView.tsx
@@ -0,0 +1,151 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import { useParams } from 'react-router-dom';
+import { useQuery } from '@tanstack/react-query';
+import LoadingSpinner from '../../components/LoadingSpinner';
+import ChartView from './ChartView';
+import SearchResults from './SearchResults';
+import { useSearchTask } from '../../hooks/useSearchTask';
+import type { Dataset } from '../../types/search';
+import './MainDatasetView.scss';
+
+const MainDatasetView: React.FC = () => {
+ const { datasetId } = useParams<{ datasetId: string }>();
+ const [isCollapsed, setIsCollapsed] = useState(true);
+
+ const datasetQuery = `?dataset_id=${datasetId}&limit=100`;
+ const { data: searchData, isFetching, error } = useSearchTask(datasetId ? datasetQuery : null, !!datasetId);
+ const searchResults = searchData?.status === 'success' ? searchData.results : [];
+ const loading = !!datasetId && (isFetching || searchData?.status === 'pending');
+
+ const { data: datasetData, error: datasetError } = useQuery({
+ queryKey: ['dataset-metadata', datasetId],
+ enabled: !!datasetId,
+ queryFn: async () => {
+ const res = await fetch(`/api/dataset?dataset_id=${encodeURIComponent(datasetId!)}`);
+ if (!res.ok) {
+ throw new Error(`Failed to load dataset metadata (HTTP ${res.status})`);
+ }
+ return (await res.json()) as Dataset;
+ },
+ });
+
+ return (
+
+
Dataset View
+
+
+
+
+ setIsCollapsed(c => !c)}>
+
+ {isCollapsed ? 'keyboard_double_arrow_right' : 'keyboard_double_arrow_left'}
+
+
+
+
+
+
+
+
+
+
+
+
Dataset Metadata
+ {datasetError ? (
+
+
Failed to load dataset metadata: {datasetError.message}
+
+ ) : (
+
+
+
+ Dataset ID:
+ {datasetData?.dataset_id ?? '(no id)'}
+
+
+ Dataset Name:
+ {datasetData?.name ?? '(no name)'}
+
+
+ Dataset Description:
+ {datasetData?.description ?? '(no description)'}
+
+
+ Number of Reactions in Dataset:
+ {datasetData?.num_reactions}
+
+
+
+ )}
+
+
+ {error ? (
+
+
Failed to load reactions: {error.message}
+
+ ) : loading ? (
+
+
+
+ ) : searchResults.length > 0 ? (
+
searchResults.length}
+ />
+ ) : (
+
+
This dataset contains no reactions.
+
+ )}
+
+
+
+
+ );
+};
+
+export default MainDatasetView;
diff --git a/app/src/views/dataset-view/MainDatasetView.vue b/app/src/views/dataset-view/MainDatasetView.vue
deleted file mode 100644
index c763e10e..00000000
--- a/app/src/views/dataset-view/MainDatasetView.vue
+++ /dev/null
@@ -1,300 +0,0 @@
-
-
-
-
-
-#dataset-main
- h1 Dataset View
- #charts(:style='this.isCollapsed ? "display: grid" : "display: flex; flex-direction: column"')
- #chartsection(:style='this.isCollapsed ? "width: 40%" : "width: 100%"')
- #chartsectioncharts(:style='this.isCollapsed ? "display: block" : "display: flex"')
- ChartView(
- uniqueId='reactantsFrequency'
- title='Frequency of Reactants'
- apiCall='input_stats'
- role='reactant'
- :isCollapsed='this.isCollapsed'
- )
- ChartView(
- uniqueId='productsFrequency'
- title='Frequency of Products'
- apiCall='product_stats'
- role='product'
- :isCollapsed='this.isCollapsed'
- )
- #expand
- button(@click='expandOrShrink')
- i.material-icons(:style='"margin-top: 15%"' :title='this.isCollapsed ? "Expand" : "Collapse"') {{ this.isCollapsed ? "keyboard_double_arrow_right" : "keyboard_double_arrow_left" }}
- #datasection
- .h4 Dataset Metadata
- table
- tr
- td Dataset ID:
- td {{datasetData.dataset_id ?? '(no id)'}}
- tr
- td Dataset Name:
- td {{datasetData.name ?? '(no name)'}}
- tr
- td Dataset Description:
- td {{datasetData.description ?? '(no description)'}}
- tr
- td Number of Reactions in Dataset:
- td {{datasetData.num_reactions}}
- .search-results
- SearchResults(
- :searchResults='searchResults'
- :isOverflow='datasetData.num_reactions > searchResults?.length'
- v-if='!loading && searchResults?.length'
- )
- .no-results(v-else-if='!loading && !searchResults?.length')
- .title This dataset contains no reactions.
- .loading(v-else)
- LoadingSpinner
-
-
-
diff --git a/app/src/views/dataset-view/SearchResults.scss b/app/src/views/dataset-view/SearchResults.scss
new file mode 100644
index 00000000..bbb72e7a
--- /dev/null
+++ b/app/src/views/dataset-view/SearchResults.scss
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// SearchResults component styles
+@use '../../styles/vars.scss' as vars;
+
+.search-results {
+ &__action-buttons {
+ margin: -2.5rem 0 1rem; // bring button row inline with title without having to pass too much into EntityTable
+ display: flex;
+ justify-content: flex-end;
+ column-gap: 0.5rem;
+
+ button {
+ padding: 0.5rem 1rem;
+ background-color: vars.$linkblue;
+ color: white;
+ border: none;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ transition: background-color 0.25s;
+
+ &:hover:not(:disabled) {
+ background-color: vars.$hoverblue;
+ }
+
+ &:disabled {
+ background-color: vars.$darkgrey;
+ cursor: not-allowed;
+ }
+ }
+ }
+
+ &__reaction-wrapper {
+ display: flex;
+ align-items: flex-start;
+ gap: 1rem;
+ margin-bottom: 1rem;
+ }
+
+ &__checkbox {
+ margin-top: 1rem;
+ cursor: pointer;
+ }
+
+ &__view-selected {
+ position: fixed;
+ bottom: 2rem;
+ right: 6.5rem;
+ z-index: 1000;
+ transition: opacity 0.25s ease-in-out;
+ }
+
+ &__view-selected-button {
+ padding: 0.5rem 1rem;
+ color: white;
+ background-color: vars.$linkblue;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ box-shadow: 0 0 5px vars.$darkgrey;
+ animation: bounce 5s infinite ease-in-out;
+
+ @keyframes bounce {
+ 0%,
+ 20% {
+ transform: translateY(0);
+ }
+ 10% {
+ transform: translateY(-10px);
+ }
+ }
+ }
+
+ @media (max-width: 1000px) {
+ margin-top: 2.5rem;
+ }
+}
diff --git a/app/src/views/dataset-view/SearchResults.tsx b/app/src/views/dataset-view/SearchResults.tsx
new file mode 100644
index 00000000..797b27ae
--- /dev/null
+++ b/app/src/views/dataset-view/SearchResults.tsx
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import EntityTable from '../../components/EntityTable';
+import ReactionCard from '../../components/ReactionCard';
+import CopyButton from '../../components/CopyButton';
+import DownloadResults from '../../components/DownloadResults';
+import type { SearchResult } from '../../types/search';
+import './SearchResults.scss';
+
+interface SearchResultsProps {
+ searchResults: SearchResult[];
+ isOverflow: boolean;
+}
+
+const SearchResults: React.FC = ({ searchResults, isOverflow }) => {
+ const [showDownloadResults, setShowDownloadResults] = useState(false);
+
+ const title = isOverflow
+ ? '100 Reactions From This Dataset (Sample)'
+ : `Reactions in this Dataset (${searchResults.length} Reactions)`;
+
+ if (searchResults.length === 0) {
+ return
;
+ }
+
+ return (
+
+
+ {entities => (
+ <>
+
+
+ setShowDownloadResults(true)}
+ >
+ Download All Search Results
+
+
+
+ {entities.map(row => (
+
+ ))}
+ >
+ )}
+
+
+
result.reaction_id)}
+ showDownloadResults={showDownloadResults}
+ onHideDownloadResults={() => setShowDownloadResults(false)}
+ />
+
+ );
+};
+
+export default SearchResults;
diff --git a/app/src/views/dataset-view/SearchResults.vue b/app/src/views/dataset-view/SearchResults.vue
deleted file mode 100644
index b866515d..00000000
--- a/app/src/views/dataset-view/SearchResults.vue
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-.search-results-main
- EntityTable(
- :tableData='formattedResults'
- :title='isOverflow ? "100 Reactions From This Dataset (Sample)" : "Reactions in this Dataset (" + formattedResults.length + " Reactions)"',
- v-slot='{ entities }'
- v-if='formattedResults.length'
- :displaySearch='false'
- )
- .action-button-holder
- CopyButton(
- :textToCopy='fullUrl'
- icon='share'
- buttonText='Shareable Link'
- )
- button(
- :disabled='!formattedResults.length'
- @click='showDownloadResults=true'
- ) Download All Search Results
- ReactionCard(
- v-for='row in entities'
- :reaction='row'
- :isSelectable='false'
- :isSelected='selectedReactions.includes(row.reaction_id)'
- @clickedSelect='updateSelectedReactions'
- )
- transition(
- name='fade'
- )
- .view-selected-container(v-if='selectedReactions.length')
- .view-selected-button(@click='goToViewSelected') View {{selectedReactions.length}} selected reactions
- DownloadResults(
- :reactionIds='formattedResults.map(result => result.reaction_id)'
- :showDownloadResults='showDownloadResults'
- @hideDownloadResults='showDownloadResults=false'
- )
-
-
-
diff --git a/app/src/views/reaction-view/CompoundView.scss b/app/src/views/reaction-view/CompoundView.scss
new file mode 100644
index 00000000..2b215323
--- /dev/null
+++ b/app/src/views/reaction-view/CompoundView.scss
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../../styles/vars' as vars;
+
+.compound-view {
+ display: grid;
+ align-items: center;
+
+ * {
+ padding: 0.5rem;
+ }
+
+ .label {
+ font-weight: 700;
+ border-bottom: 1px solid #000;
+ }
+
+ .raw {
+ .button {
+ padding: 0.5rem;
+ background-color: vars.$darkgrey;
+ color: white;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ }
+ }
+
+ .data {
+ width: 100%;
+ height: 100%;
+ overflow-x: auto;
+ }
+}
diff --git a/app/src/views/reaction-view/CompoundView.tsx b/app/src/views/reaction-view/CompoundView.tsx
new file mode 100644
index 00000000..28a3798d
--- /dev/null
+++ b/app/src/views/reaction-view/CompoundView.tsx
@@ -0,0 +1,197 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect, useMemo } from 'react';
+import reaction_pb from 'ord-schema';
+import type { Compound, ProductCompound } from 'ord-schema/proto/reaction_pb';
+import FloatingModal from '../../components/FloatingModal';
+import { amountObj, amountStr } from '../../utils/amount';
+import { enumName } from '../../utils/enum';
+import './CompoundView.scss';
+
+// CompoundView is invoked with both reaction inputs (Compound) and outcome
+// products (ProductCompound); the two protobuf messages share most rendered
+// fields but each carries a few of its own (Compound: reactionRole / preparationsList,
+// ProductCompound: isDesiredProduct / isolatedColor / texture).
+type ComponentLike = Partial & Partial;
+
+interface CompoundViewProps {
+ component: ComponentLike | undefined;
+}
+
+interface RawData {
+ identifiers?: Array<{ type: string; value: string }>;
+ amount?: Record;
+ preparations?: Array<{ type: string; details: string }>;
+ is_desired_product?: boolean;
+ isolated_color?: string;
+ texture?: { type: string; details?: string };
+ reaction_role: string;
+}
+
+const CompoundView: React.FC = ({ component }) => {
+ const [compoundSVG, setCompoundSVG] = useState(null);
+ const [showRawData, setShowRawData] = useState(false);
+
+ const compoundAmountObj = useMemo(() => amountObj(component?.amount), [component?.amount]);
+ const compoundAmount = useMemo(() => amountStr(compoundAmountObj), [compoundAmountObj]);
+
+ const compoundRole = useMemo(() => {
+ if (!component?.reactionRole) return '';
+ return String(enumName(reaction_pb.ReactionRole.ReactionRoleType, component.reactionRole) ?? '');
+ }, [component?.reactionRole]);
+
+ const rawData: RawData = useMemo(() => {
+ const returnObj: RawData = { reaction_role: compoundRole };
+
+ if (component?.identifiersList?.length) {
+ returnObj.identifiers = component.identifiersList.map(identifier => ({
+ type: String(enumName(reaction_pb.CompoundIdentifier.CompoundIdentifierType, identifier.type) ?? ''),
+ value: identifier.value,
+ }));
+ }
+
+ if (component?.amount && compoundAmountObj.unitCategory) {
+ returnObj.amount = {
+ [compoundAmountObj.unitCategory]: {
+ value: compoundAmountObj.unitAmount,
+ units: compoundAmountObj.unitType ?? '',
+ },
+ };
+ }
+
+ if (component?.preparationsList?.length) {
+ returnObj.preparations = component.preparationsList.map(prep => ({
+ type: String(enumName(reaction_pb.CompoundPreparation.CompoundPreparationType, prep.type) ?? ''),
+ details: prep.details,
+ }));
+ }
+
+ if (component?.isDesiredProduct) {
+ returnObj.is_desired_product = component.isDesiredProduct;
+ }
+
+ if (component?.isolatedColor) {
+ returnObj.isolated_color = component.isolatedColor;
+ }
+
+ if (component?.texture) {
+ const textureObj: { type: string; details?: string } = {
+ type: component.texture.type !== undefined ? String(component.texture.type) : '',
+ };
+ if (component.texture.details) {
+ textureObj.details = component.texture.details;
+ }
+ returnObj.texture = textureObj;
+ }
+
+ return returnObj;
+ }, [component, compoundRole, compoundAmountObj]);
+
+ const gridColumns = useMemo(() => `1fr repeat(${component?.amount ? 3 : 2}, auto)`, [component?.amount]);
+
+ const smilesValue = useMemo(() => {
+ if (!component?.identifiersList?.length) return null;
+ const smilesType = reaction_pb.CompoundIdentifier.CompoundIdentifierType.SMILES;
+ const smilesIdentifier = component.identifiersList.find(identifier => identifier.type === smilesType);
+ return smilesIdentifier?.value ?? null;
+ }, [component?.identifiersList]);
+
+ useEffect(() => {
+ if (!smilesValue) return;
+
+ // prep compound
+ const compound = new reaction_pb.Compound();
+ const identifier = compound.addIdentifiers();
+ identifier.setValue(smilesValue);
+ identifier.setType(reaction_pb.CompoundIdentifier.CompoundIdentifierType.SMILES);
+
+ const fetchSVG = async () => {
+ try {
+ const response = await fetch('/api/compound_svg', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-protobuf',
+ },
+ body: compound.serializeBinary() as BodyInit,
+ });
+
+ // Skip the 4xx/5xx body — it's an HTML error page that
+ // dangerouslySetInnerHTML would render verbatim in the SVG slot.
+ if (!response.ok) {
+ console.error(`compound_svg failed (HTTP ${response.status})`);
+ return;
+ }
+ const result = await response.json();
+ setCompoundSVG(result);
+ } catch (error) {
+ console.error('Error fetching compound SVG:', error);
+ }
+ };
+
+ fetchSVG();
+ }, [smilesValue]);
+
+ return (
+
+
Compound
+ {component?.amount &&
Amount
}
+
Role
+
Raw
+
+
+ {component?.amount &&
{compoundAmount}
}
+
{compoundRole.toLowerCase()}
+
+
setShowRawData(true)}
+ >
+ <>
+
+
+
+ {showRawData && (
+
setShowRawData(false)}
+ >
+
+ {rawData.identifiers?.map((identifier, index) => (
+
{`identifiers: ${JSON.stringify(identifier)}`}
+ ))}
+ {rawData.amount &&
amount: {JSON.stringify(rawData.amount)} }
+
reaction_role: {rawData.reaction_role}
+ {rawData.is_desired_product &&
is_desired_product: {rawData.is_desired_product} }
+ {rawData.isolated_color &&
isolated_color: {rawData.isolated_color} }
+ {rawData.texture &&
texture: {JSON.stringify(rawData.texture)} }
+ {rawData.preparations?.map((prep, index) => (
+
preparations: {JSON.stringify(prep)}
+ ))}
+
+
+ )}
+
+ );
+};
+
+export default CompoundView;
diff --git a/app/src/views/reaction-view/CompoundView.vue b/app/src/views/reaction-view/CompoundView.vue
deleted file mode 100644
index ca3ecd7a..00000000
--- a/app/src/views/reaction-view/CompoundView.vue
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-.compound-view(
- :style='{gridTemplateColumns: gridColumns}'
-)
- .label Compound
- .label(v-if='component.amount') Amount
- .label Role
- .label Raw
- .svg(
- v-html='compoundSVG'
- )
- .amount(v-if='component.amount') {{compoundAmount}}
- .role {{compoundRole.toLowerCase()}}
- .raw
- .button(@click='showRawData=true') <>
- floating-modal(
- v-if='showRawData'
- title="Raw Data"
- @closeModal='showRawData=false'
- )
- .data
- pre(v-for='identifier in rawData.identifiers') identifiers: {{identifier}}
- pre(v-if='rawData.amount') amount: {{rawData.amount}}
- pre reaction_role: {{rawData.reaction_role}}
- pre(v-if='rawData.is_desired_product') is_desired_product: {{rawData.is_desired_product}}
- pre(v-if='rawData.isolated_color') isolated_color: {{rawData.isolated_color}}
- pre(v-if='rawData.texture') texture: {{rawData.texture}}
- pre(v-for='prep in rawData.preparations') preparations: {{prep}}
-
-
-
diff --git a/app/src/views/reaction-view/ConditionsView.scss b/app/src/views/reaction-view/ConditionsView.scss
new file mode 100644
index 00000000..162fc67e
--- /dev/null
+++ b/app/src/views/reaction-view/ConditionsView.scss
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.conditions-view {
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+ }
+}
diff --git a/app/src/views/reaction-view/ConditionsView.tsx b/app/src/views/reaction-view/ConditionsView.tsx
new file mode 100644
index 00000000..ec8516a6
--- /dev/null
+++ b/app/src/views/reaction-view/ConditionsView.tsx
@@ -0,0 +1,242 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+ electrochemType,
+ flowType,
+ illumType,
+ lengthStr,
+ pressureAtmo,
+ pressureSetPoint,
+ pressureType,
+ stirRate,
+ stirType,
+ tempSetPoint,
+ tempType,
+} from '../../utils/conditions';
+import type { ReactionConditionsData } from '../../types/search';
+import './ConditionsView.scss';
+
+interface ConditionsViewProps {
+ conditions: ReactionConditionsData | undefined;
+ display: string;
+}
+
+const ConditionsView: React.FC = ({ conditions, display }) => {
+ if (!conditions) return null;
+
+ if (display === 'temperature') {
+ const t = conditions.temperature;
+ return (
+
+
+
Control Type
+
{tempType(t?.control?.type)}
+ {t?.control?.details && (
+ <>
+
Details
+
{t.control.details}
+ >
+ )}
+
Setpoint
+
{tempSetPoint(t?.setpoint)}
+ {t?.measurementsList && t.measurementsList.length > 0 && (
+ <>
+
Measurements
+ {/* TODO: render temperature measurements once the schema's shape is fully ported. */}
+
{t.measurementsList.length} recorded
+ >
+ )}
+
+
+ );
+ }
+
+ if (display === 'pressure') {
+ const p = conditions.pressure;
+ return (
+
+
+
Control Type
+
{pressureType(p?.control?.type)}
+ {p?.control?.details && (
+ <>
+
Details
+
{p.control.details}
+ >
+ )}
+
Setpoint
+
{pressureSetPoint(p?.setpoint)}
+
Atmosphere
+
{pressureAtmo(p?.atmosphere)}
+ {p?.measurementsList && p.measurementsList.length > 0 && (
+ <>
+
Measurements
+ {/* TODO: render pressure measurements once the schema's shape is fully ported. */}
+
{p.measurementsList.length} recorded
+ >
+ )}
+
+
+ );
+ }
+
+ if (display === 'stirring') {
+ const s = conditions.stirring;
+ return (
+
+
+
Type
+
{stirType(s?.type)}
+ {s?.details && (
+ <>
+
Details
+
{s.details}
+ >
+ )}
+
Rate
+
{stirRate(s?.rate) || 'UNSPECIFIED'}
+ {s?.rate?.rpm !== undefined && s.rate.rpm !== 0 && (
+ <>
+
RPM
+
{s.rate.rpm}
+ >
+ )}
+
+
+ );
+ }
+
+ if (display === 'illumination') {
+ const i = conditions.illumination;
+ const distance = lengthStr(i?.distanceToVessel);
+ return (
+
+
+
Type
+
{illumType(i)}
+
Peak Wavelength
+
{lengthStr(i?.peakWavelength) ?? 'None'}
+ {i?.color && (
+ <>
+
Color
+
{i.color}
+ >
+ )}
+
Distance to Vessel
+
{distance ?? 'None'}
+
+
+ );
+ }
+
+ if (display === 'electrochemistry') {
+ const e = conditions.electrochemistry;
+ return (
+
+
+
Type
+
{electrochemType(e?.type)}
+ {e?.details && (
+ <>
+
Details
+
{e.details}
+ >
+ )}
+ {e?.anodeMaterial && (
+ <>
+
Anode
+
{e.anodeMaterial}
+ >
+ )}
+ {e?.cathodeMaterial && (
+ <>
+
Cathode
+
{e.cathodeMaterial}
+ >
+ )}
+ {/* TODO: render current, voltage, electrodeSeparation, and measurementsList. */}
+
+
+ );
+ }
+
+ if (display === 'flow') {
+ const f = conditions.flow;
+ return (
+
+
+
Type
+
{flowType(f?.type)}
+ {f?.details && (
+ <>
+
Details
+
{f.details}
+ >
+ )}
+ {f?.pumpType && (
+ <>
+
Pump Type
+
{f.pumpType}
+ >
+ )}
+ {/* TODO: render tubing dimensions if/when needed. */}
+
+
+ );
+ }
+
+ if (display === 'other') {
+ return (
+
+
+ {conditions.reflux && (
+ <>
+
Reflux
+
yes
+ >
+ )}
+ {/* See WorkupsView for the proto3 zero-default tradeoff: 0 means
+ "default unset" *or* a real, strongly-acidic pH. Match the Vue
+ v-if='conditions.ph' behavior and hide on 0. */}
+ {conditions.ph !== undefined && conditions.ph !== 0 && (
+ <>
+
pH
+
{conditions.ph}
+ >
+ )}
+ {conditions.conditionsAreDynamic && (
+ <>
+
Conditions are dynamic
+
yes
+ >
+ )}
+ {conditions.details && (
+ <>
+
Details
+
{conditions.details}
+ >
+ )}
+
+
+ );
+ }
+
+ return null;
+};
+
+export default ConditionsView;
diff --git a/app/src/views/reaction-view/ConditionsView.vue b/app/src/views/reaction-view/ConditionsView.vue
deleted file mode 100644
index 01c87529..00000000
--- a/app/src/views/reaction-view/ConditionsView.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
-
-
-.conditions-view
- .temperature.details(v-if='display==="temperature"')
- .label Control Type
- .value {{tempType}}
- template(v-if='conditions.temperature?.control.details')
- .label Details
- .value {{conditions.temperature.control.details}}
- .label Setpoint
- .value {{tempSetPoint}}
- // TODO Flesh out temp measurements
- template(v-if='conditions.temperature?.measurementsList?.length')
- .label Measurements
- .value {{conditions.temperature.measurementsList}}
-
- .pressure.details(v-if='display==="pressure"')
- .label Control Type
- .value {{pressureType}}
- template(v-if='conditions.pressure?.control.details')
- .label Details
- .value {{conditions.pressure.control.details}}
- .label Setpoint
- .value {{pressureSetPoint}}
- .label Atmosphere
- .value {{pressureAtmo}}
- // TODO Flesh out pressure measurements
- template(v-if='conditions.pressure?.measurementsList?.length')
- .label Measurements
- .value {{conditions.pressure.measurementsList}}
-
- .stirring.details(v-if='display==="stirring"')
- .label Type
- .value {{stirType}}
- template(v-if='conditions.stirring?.details')
- .label Details
- .value {{conditions.stirring.details}}
- .label Rate
- .value {{stirRate || "UNSPECIFIED"}}
- template(v-if='conditions.stirring?.rate?.rpm')
- .label RPM
- .value {{conditions.stirring.rate.rpm}}
-
- .illumination.details(v-if='display==="illumination"')
- .label Type
- .value {{illumType}}
- // TODO flesh out wave length
- .label Peak Wavelength
- .value {{conditions.illumination.peakWaveLength || "None"}}
- template(v-if='conditions.illumination?.color')
- .label Color
- .value {{conditions.illumination.color}}
- // TODO flesh out distance
- .label Distance to Vessel
- .value {{conditions.illumination.distanceToVessel || "None"}}
-
- // TODO flesh out electrochemistry
- .electro.details(v-if='display === "electrochemistry"')
- .label Type
- .value {{conditions.electrochemistry}}
-
- // TODO flesh out flow
- .electro.details(v-if='display === "flow"')
- .label Type
- .value {{conditions.flow}}
-
- // TODO flesh out other
- .other.details(v-if='display === "other"')
- template(v-if='conditions.reflux')
- .label Reflux
- .value {{conditions.reflux}}
- template(v-if='conditions.ph')
- .label pH
- .value {{conditions.ph}}
- template(v-if='conditions.conditions_are_dynamic')
- .label Conditions are dynamic
- .value {{conditions.conditions_are_dynamic}}
- template(v-if='conditions.details')
- .label Details
- .value {{conditions.details}}
-
-
-
diff --git a/app/src/views/reaction-view/EventsView.scss b/app/src/views/reaction-view/EventsView.scss
new file mode 100644
index 00000000..5e17ad4b
--- /dev/null
+++ b/app/src/views/reaction-view/EventsView.scss
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../../styles/vars' as vars;
+
+.events-view {
+ .tabs {
+ display: flex;
+ column-gap: 0.5rem;
+ row-gap: 0.5rem;
+ margin-bottom: 0.5rem;
+ flex-wrap: wrap;
+
+ .tab {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ border: 1px solid vars.$medgrey;
+ cursor: pointer;
+ transition: 0.25s;
+
+ &.selected {
+ background-color: vars.$linkblue;
+ color: white;
+ border-color: vars.$linkblue;
+ cursor: default;
+ }
+
+ &.capitalize {
+ text-transform: capitalize;
+ }
+ }
+ }
+
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+ }
+}
diff --git a/app/src/views/reaction-view/EventsView.tsx b/app/src/views/reaction-view/EventsView.tsx
new file mode 100644
index 00000000..df161e78
--- /dev/null
+++ b/app/src/views/reaction-view/EventsView.tsx
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import type { RecordEvent } from 'ord-schema/proto/reaction_pb';
+import './EventsView.scss';
+
+interface EventsViewProps {
+ events: RecordEvent.AsObject[];
+}
+
+const EventsView: React.FC = ({ events }) => {
+ const [eventIdx, setEventIdx] = useState(0);
+
+ if (!events?.length) return null;
+
+ const currentEvent = events[eventIdx];
+
+ return (
+
+
+ {events.map((event, idx) => (
+
setEventIdx(idx)}
+ >
+ {event.time?.value ? new Date(event.time.value).toLocaleString() : ''}
+
+ ))}
+
+
+
+ {currentEvent?.details && (
+ <>
+
Details
+
{currentEvent.details}
+ >
+ )}
+
+ {currentEvent?.person?.username && (
+ <>
+
Username
+
{currentEvent.person.username}
+ >
+ )}
+
+ {currentEvent?.person?.name && (
+ <>
+
Name
+
{currentEvent.person.name}
+ >
+ )}
+
+ {currentEvent?.person?.orcid && (
+ <>
+
ORCID
+
{currentEvent.person.orcid}
+ >
+ )}
+
+ {currentEvent?.person?.organization && (
+ <>
+
Organization
+
{currentEvent.person.organization}
+ >
+ )}
+
+ {currentEvent?.person?.email && (
+ <>
+
Email
+
{currentEvent.person.email}
+ >
+ )}
+
+
+ );
+};
+
+export default EventsView;
diff --git a/app/src/views/reaction-view/EventsView.vue b/app/src/views/reaction-view/EventsView.vue
deleted file mode 100644
index 875872c7..00000000
--- a/app/src/views/reaction-view/EventsView.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-.events-view
- .tabs
- .tab(
- v-for='(event, idx) in events'
- @click='eventIdx = idx'
- :class='eventIdx === idx ? "selected" : ""'
- ) {{new Date(event.time.value).toLocaleString()}}
- .details
- template(v-if='events[eventIdx].details')
- .label Details
- .value {{events[eventIdx].details}}
- template(v-if='events[eventIdx].person?.username')
- .label Username
- .value {{events[eventIdx].person.username}}
- template(v-if='events[eventIdx].person?.name')
- .label Name
- .value {{events[eventIdx].person.name}}
- template(v-if='events[eventIdx].person?.orcid')
- .label ORCID
- .value {{events[eventIdx].person.orcid}}
- template(v-if='events[eventIdx].person?.organization')
- .label Organization
- .value {{events[eventIdx].person.organization}}
- template(v-if='events[eventIdx].person?.email')
- .label Email
- .value {{events[eventIdx].person.email}}
-
-
-
-
diff --git a/app/src/views/reaction-view/MainReactionView.scss b/app/src/views/reaction-view/MainReactionView.scss
new file mode 100644
index 00000000..7d6d634b
--- /dev/null
+++ b/app/src/views/reaction-view/MainReactionView.scss
@@ -0,0 +1,132 @@
+/*
+ Copyright 2023 Open Reaction Database Project Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+@use '../../styles/vars.scss' as vars;
+@use '../../styles/tabs.scss' as tabs;
+@use '../../styles/transition.scss' as transition;
+
+.main-reaction-view {
+ min-height: 90vh;
+
+ .reaction-transition {
+ margin: 2rem 0;
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ }
+
+ .nav-holder {
+ height: 100%;
+
+ .nav {
+ background-color: white;
+ border-radius: 0.25rem;
+ box-sizing: border-box;
+ margin-left: 1rem;
+ min-width: 8rem;
+ width: 15vw;
+ height: auto;
+ max-height: 70vh;
+ overflow-x: hidden;
+ overflow-y: auto;
+ position: sticky;
+ top: 1rem;
+ display: block;
+
+ .nav-item {
+ padding: 0.5rem 1rem;
+ text-transform: capitalize;
+ cursor: pointer;
+ transition: 0.16s;
+ color: black;
+
+ &.active {
+ color: white;
+ background-color: vars.$linkblue;
+ }
+ }
+ }
+ }
+
+ .loading {
+ position: absolute;
+ width: 100%;
+ padding-top: 15vh;
+ }
+
+ .title {
+ font-weight: 700;
+ font-size: 2rem;
+ margin-bottom: 0.5rem;
+ }
+
+ .section,
+ .title {
+ width: calc(85vw - 3rem);
+ min-width: calc(800px - 11rem); // (min main width) - (nav width plus gutters)
+ margin: 0;
+ }
+
+ .section {
+ background-color: white;
+ border-radius: 0.25rem;
+ margin-bottom: 1rem;
+ padding: 1rem;
+ box-sizing: border-box;
+
+ .title {
+ font-size: 1.5rem;
+ }
+
+ summary {
+ overflow-x: scroll;
+ }
+
+ .identifiers {
+ display: grid;
+ grid-template-columns: auto auto 1fr;
+ column-gap: 1rem;
+ }
+
+ .input {
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ margin-bottom: 1rem;
+
+ .label {
+ &:first-letter {
+ text-transform: uppercase;
+ }
+ }
+ }
+
+ .compound {
+ width: fit-content;
+ }
+ }
+
+ .full-record.button {
+ padding: 0.5rem 1rem;
+ background-color: vars.$linkblue;
+ border-radius: 0.25rem;
+ width: fit-content;
+ color: white;
+ cursor: pointer;
+ }
+ }
+}
diff --git a/app/src/views/reaction-view/MainReactionView.tsx b/app/src/views/reaction-view/MainReactionView.tsx
new file mode 100644
index 00000000..a736d0e9
--- /dev/null
+++ b/app/src/views/reaction-view/MainReactionView.tsx
@@ -0,0 +1,501 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import { useParams } from 'react-router-dom';
+import reaction_pb from 'ord-schema';
+import type { ReactionInput, RecordEvent } from 'ord-schema/proto/reaction_pb';
+import CompoundView from './CompoundView';
+import SetupView from './SetupView';
+import ConditionsView from './ConditionsView';
+import NotesView from './NotesView';
+import ObservationsView from './ObservationsView';
+import WorkupsView from './WorkupsView';
+import OutcomesView from './OutcomesView';
+import ProvenanceView from './ProvenanceView';
+import EventsView from './EventsView';
+import FloatingModal from '../../components/FloatingModal';
+import LoadingSpinner from '../../components/LoadingSpinner';
+import { base64ToBytes } from '../../utils/base64';
+import { enumName } from '../../utils/enum';
+import { formattedTime } from '../../utils/outcomes';
+import type { ReactionData } from '../../types/search';
+import './MainReactionView.scss';
+
+const MainReactionView: React.FC = () => {
+ const { reactionId } = useParams<{ reactionId: string }>();
+ const [reaction, setReaction] = useState(null);
+ const [reactionSummary, setReactionSummary] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [inputsIdx, setInputsIdx] = useState(0);
+ const [setupTab, setSetupTab] = useState('vessel');
+ const [conditionTab, setConditionTab] = useState('temperature');
+ const [workupsTab, setWorkupsTab] = useState(0);
+ const [outcomesTab, setOutcomesTab] = useState(0);
+ const [showRawReaction, setShowRawReaction] = useState(false);
+ const [navItems, setNavItems] = useState([]);
+ const [activeNav, setActiveNav] = useState('summary');
+
+ const setupTabs = ['vessel', 'environment', 'automation'];
+ const conditionTabs = ['temperature', 'pressure', 'stirring', 'illumination', 'electrochemistry', 'flow', 'other'];
+
+ const getReactionData = useCallback(async (): Promise => {
+ if (!reactionId) return null;
+
+ try {
+ const response = await fetch('/api/reactions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ reaction_ids: [reactionId] }),
+ });
+
+ if (!response.ok) return null;
+ const data = (await response.json()) as Array<{ proto: string }>;
+ if (!data?.[0]?.proto) return null;
+
+ const bytes = base64ToBytes(data[0].proto);
+ const decoded = reaction_pb.Reaction.deserializeBinary(new Uint8Array(bytes)).toObject();
+
+ // Sort inputs by their declared addition order.
+ decoded.inputsMap?.sort((a, b) => (a[1].additionOrder ?? 0) - (b[1].additionOrder ?? 0));
+
+ return decoded;
+ } catch (error) {
+ console.error('Error fetching reaction data:', error);
+ return null;
+ }
+ }, [reactionId]);
+
+ const getReactionSummary = useCallback(async (): Promise => {
+ if (!reactionId) return '';
+
+ try {
+ const response = await fetch(`/api/reaction_summary?reaction_id=${reactionId}&compact=false`);
+ // Don't pipe a 4xx/5xx HTML error body into the summary panel via
+ // dangerouslySetInnerHTML; return empty so the section stays blank.
+ if (!response.ok) {
+ console.error(`reaction_summary failed (HTTP ${response.status}) for ${reactionId}`);
+ return '';
+ }
+ return await response.text();
+ } catch (error) {
+ console.error('Error fetching reaction summary:', error);
+ return '';
+ }
+ }, [reactionId]);
+
+ const displayDetails = useMemo>(() => {
+ const inputEntry = reaction?.inputsMap?.[inputsIdx];
+ if (!inputEntry) return {};
+ const input: ReactionInput.AsObject = inputEntry[1];
+
+ const raw = { ...(input as unknown as Record) };
+ const formatted: Record = {};
+
+ for (const [key, value] of Object.entries(raw)) {
+ if (value == null || Array.isArray(value)) continue;
+ formatted[key] = value as React.ReactNode;
+ }
+
+ if (input.additionDevice?.type !== undefined) {
+ formatted.additionDevice =
+ enumName(
+ reaction_pb.ReactionInput.AdditionDevice.AdditionDeviceType,
+ input.additionDevice.type,
+ )?.toLowerCase() ?? '';
+ }
+
+ if (input.additionSpeed?.type !== undefined) {
+ formatted.additionSpeed =
+ enumName(reaction_pb.ReactionInput.AdditionSpeed.AdditionSpeedType, input.additionSpeed.type)?.toLowerCase() ??
+ '';
+ }
+
+ if (input.additionDuration) {
+ formatted.additionDuration = formattedTime(input.additionDuration) ?? '';
+ }
+
+ return formatted;
+ }, [reaction, inputsIdx]);
+
+ const displayConditionsOther = useMemo(() => {
+ const conditions = reaction?.conditions;
+ if (!conditions) return undefined;
+ const otherFields: Array = ['reflux', 'ph', 'conditionsAreDynamic', 'details'];
+ return otherFields.find(key => conditions[key]);
+ }, [reaction?.conditions]);
+
+ const events = useMemo(() => {
+ const provenance = reaction?.provenance;
+ if (!provenance?.recordCreated) return [];
+
+ const created: RecordEvent.AsObject = { ...provenance.recordCreated, details: '(record created)' };
+ const modified = provenance.recordModifiedList ?? [];
+ const all = [created, ...modified];
+
+ all.sort((a, b) => {
+ const aMs = a.time?.value ? new Date(a.time.value).getTime() : 0;
+ const bMs = b.time?.value ? new Date(b.time.value).getTime() : 0;
+ return aMs - bMs;
+ });
+
+ return all;
+ }, [reaction?.provenance]);
+
+ const getReactionType = (id: number): string =>
+ enumName(reaction_pb.ReactionIdentifier.ReactionIdentifierType, id) ?? '';
+
+ const getWorkupLabel = (type: number): string => {
+ const name = enumName(reaction_pb.ReactionWorkup.ReactionWorkupType, type);
+ return name ? name.toLowerCase().replace(/_/g, ' ') : '';
+ };
+
+ const scrollTo = (id: string) => {
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ useEffect(() => {
+ const fetchData = async () => {
+ if (!reactionId) return;
+
+ const [reactionData, summaryData] = await Promise.all([getReactionData(), getReactionSummary()]);
+
+ if (reactionData) {
+ setReaction(reactionData);
+ setReactionSummary(summaryData);
+
+ const items = ['summary', 'identifiers', 'inputs'];
+ const optionals: Array = [
+ 'setup',
+ 'conditions',
+ 'notes',
+ 'observationsList',
+ 'workupsList',
+ ];
+
+ optionals.forEach(key => {
+ const value = reactionData[key];
+ if (Array.isArray(value) ? value.length > 0 : Boolean(value)) {
+ items.push(key.replace(/List$/, ''));
+ }
+ });
+
+ items.push('outcomes', 'provenance', 'full-record');
+ setNavItems(items);
+ }
+
+ setLoading(false);
+ };
+
+ fetchData();
+ }, [reactionId, getReactionData, getReactionSummary]);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrollPosition = window.scrollY + 100;
+
+ for (let i = navItems.length - 1; i >= 0; i--) {
+ const element = document.getElementById(navItems[i]);
+ if (element) {
+ const elementTop = element.offsetTop;
+ if (scrollPosition >= elementTop) {
+ setActiveNav(navItems[i]);
+ break;
+ }
+ }
+ }
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ handleScroll();
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, [navItems]);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+ {navItems.map(item => (
+
scrollTo(item)}
+ >
+ {item.replace(/-/g, ' ')}
+
+ ))}
+
+
+
+
+
Summary
+
+ {reactionSummary && (
+
+ )}
+
+
+ {reaction?.identifiersList && reaction.identifiersList.length > 0 && (
+
+
Identifiers
+
+
+ {reaction.identifiersList.map((identifier, index) => (
+
+ {getReactionType(identifier.type)}
+ {identifier.value}
+ {identifier.details}
+
+ ))}
+
+
+
+ )}
+
+ {reaction?.inputsMap && reaction.inputsMap.length > 0 && (
+
+ )}
+
+ {reaction?.setup && (
+
+
Setup
+
+
+ {setupTabs.map(
+ tab =>
+ (tab !== 'automation' || reaction.setup?.isAutomated) && (
+
setSetupTab(tab)}
+ >
+ {tab}
+
+ ),
+ )}
+
+
+
+
+
+
+ )}
+
+ {reaction?.conditions && (
+
+
Conditions
+
+
+ {conditionTabs.map(
+ tab =>
+ (reaction.conditions?.[tab as keyof typeof reaction.conditions] ||
+ (tab === 'other' && displayConditionsOther)) && (
+
setConditionTab(tab)}
+ >
+ {tab}
+
+ ),
+ )}
+
+
+
+
+
+
+ )}
+
+ {reaction?.notes && (
+
+ )}
+
+ {reaction?.observationsList && reaction.observationsList.length > 0 && (
+
+ )}
+
+ {reaction?.workupsList && reaction.workupsList.length > 0 && (
+
+
Workups
+
+
+ {reaction.workupsList.map((workup, idx) => (
+
setWorkupsTab(idx)}
+ >
+ {getWorkupLabel(workup.type)}
+
+ ))}
+
+
+
+
+
+
+ )}
+
+ {reaction?.outcomesList && reaction.outcomesList.length > 0 && (
+
+
Outcomes
+
+
+ {reaction.outcomesList.map((_outcome, idx) => (
+
setOutcomesTab(idx)}
+ >
+ Outcome {idx + 1}
+
+ ))}
+
+
+ {/* key=outcomesTab so the inner tab/modal state resets when the user switches outcomes. */}
+
+
+
+
+ )}
+
+ {reaction?.provenance && (
+
+ )}
+
+ {events.length > 0 && (
+
+ )}
+
+ {reaction && (
+
+
Full Record
+
+
setShowRawReaction(true)}
+ >
+ View Full Record
+
+
+
+ )}
+
+ {showRawReaction && reaction && (
+
setShowRawReaction(false)}
+ >
+
+
{JSON.stringify(reaction, null, 2)}
+
+
+ )}
+
+
+
+ );
+};
+
+export default MainReactionView;
diff --git a/app/src/views/reaction-view/MainReactionView.vue b/app/src/views/reaction-view/MainReactionView.vue
deleted file mode 100644
index 4615a840..00000000
--- a/app/src/views/reaction-view/MainReactionView.vue
+++ /dev/null
@@ -1,426 +0,0 @@
-
-
-
-
-
-.main-reaction-view
- transition(name="fade")
- .loading(v-if='loading')
- LoadingSpinner
- transition(name="fade")
- .reaction-transition(v-if='!loading')
- .nav-holder
- .nav
- .nav-item(
- v-for='item in navItems'
- :class='activeNav == item ? "active" : ""'
- @click='scrollTo(item)'
- ) {{item.replaceAll("-"," ")}}
- .content
- .title Summary
- #summary.section
- .summary(v-if='reactionSummary')
- .display(v-html='reactionSummary')
- #identifiers(v-if='reaction?.identifiersList?.length')
- .title Identifiers
- .section
- .identifiers
- template(v-for='identifier in reaction.identifiersList')
- .value {{getReactionType(identifier.type)}}
- .value {{identifier.value}}
- .value {{identifier.details}}
- #inputs(v-if='reaction?.inputsMap?.length')
- .title Inputs
- .section
- .tabs
- .tab(
- v-for='(input, idx) in reaction.inputsMap'
- @click='inputsIdx = idx'
- :class='inputsIdx == idx ? "selected" : ""'
- ) {{input[0]}}
- .input
- .title Details
- .details
- template(v-for='key in Object.keys(displayDetails)')
- .label {{key.replaceAll(/(?=[A-Z])/g, ' ')}}
- .value {{displayDetails[key]}}
- .title Components
- .compound
- template(v-for='component in reaction.inputsMap[inputsIdx][1].componentsList')
- CompoundView(
- :component='component'
- )
- #setup(v-if='reaction?.setup')
- .title Setup
- .section
- .tabs
- template(
- v-for='tab in setupTabs'
- )
- .tab.capitalize(
- @click='setupTab = tab'
- :class='setupTab === tab ? "selected" : ""'
- v-if='tab !== "automation" || reaction.setup.is_automated'
- ) {{tab}}
- .details
- SetupView(
- :setup='reaction.setup'
- :display='setupTab'
- )
- #conditions(v-if='reaction?.conditions')
- .title Conditions
- .section
- .tabs
- template(
- v-for='tab in conditionTabs'
- )
- .tab.capitalize(
- @click='conditionTab = tab'
- :class='conditionTab === tab ? "selected" : ""'
- v-if='reaction.conditions[tab] || (tab === "other" && displayConditionsOther)'
- ) {{tab}}
- .details
- ConditionsView(
- :conditions='reaction.conditions'
- :display='conditionTab'
- )
- #notes(v-if='reaction.notes')
- .title Notes
- .section
- .details
- NotesView(
- :notes='reaction.notes'
- )
- #observations(v-if='reaction.observationsList?.length')
- .title Observations
- .section
- .details
- ObservationsView(
- :observations='reaction.observationsList'
- )
- // TODO flesh out workups section
- #workups(v-if='reaction.workupsList?.length')
- .title Workups
- .section
- .tabs
- .tab.capitalize(
- v-for='(workup, idx) in reaction.workupsList'
- @click='workupsTab = idx'
- :class='workupsTab === idx ? "selected" : ""'
- ) {{getWorkupLabel(workup.type)}}
- .details
- WorkupsView(
- :workup='reaction.workupsList[workupsTab]'
- )
- #outcomes(v-if='reaction.outcomesList?.length')
- .title Outcomes
- .section
- .tabs
- .tab.capitalize(
- v-for='(outcome, idx) in reaction.outcomesList'
- @click='outcomesTab = idx'
- :class='outcomesTab === idx ? "selected" : ""'
- ) Outcome {{idx + 1}}
- .details
- OutcomesView(
- :outcome='reaction.outcomesList[outcomesTab]'
- )
- #provenance(v-if='reaction.provenance')
- .title Provenance
- .section
- ProvenanceView(:provenance='reaction.provenance')
- #events(v-if='events?.length')
- .title Record Events
- .section
- EventsView(:events='events')
- #full-record(v-if='reaction')
- .title Full Record
- .section
- .full-record.button(@click='showRawReaction=true') View Full Record
- FloatingModal(
- v-if='showRawReaction'
- title="Raw Data"
- @closeModal='showRawReaction=false'
- )
- .data
- pre {{reaction}}
-
-
-
diff --git a/app/src/views/reaction-view/NotesView.scss b/app/src/views/reaction-view/NotesView.scss
new file mode 100644
index 00000000..1ea9d69a
--- /dev/null
+++ b/app/src/views/reaction-view/NotesView.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.notes-view {
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+
+ .label {
+ &:first-letter {
+ text-transform: capitalize;
+ }
+ }
+ }
+}
diff --git a/app/src/views/reaction-view/NotesView.tsx b/app/src/views/reaction-view/NotesView.tsx
new file mode 100644
index 00000000..3498f9fc
--- /dev/null
+++ b/app/src/views/reaction-view/NotesView.tsx
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useMemo } from 'react';
+import type { ReactionNotesData } from '../../types/search';
+import './NotesView.scss';
+
+interface NotesViewProps {
+ notes: ReactionNotesData | undefined;
+}
+
+interface NoteToDisplay {
+ val: string;
+ label: string;
+}
+
+const NotesView: React.FC = ({ notes }) => {
+ // Render camelCase field names as space-separated lowercase phrases,
+ // stripping the "is" prefix on boolean flags like `isHeterogeneous`.
+ const camelToSpaces = (str: string): string =>
+ str
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
+ .replace(/^is /, '')
+ .toLowerCase();
+
+ const notesToDisplay = useMemo((): NoteToDisplay[] => {
+ if (!notes) return [];
+
+ const fields = notes as Record;
+ return Object.keys(fields)
+ .filter(key => Boolean(fields[key]))
+ .map(key => ({
+ val: String(fields[key]),
+ label: camelToSpaces(key),
+ }));
+ }, [notes]);
+
+ return (
+
+
+ {notesToDisplay.map((note, index) => (
+
+ {note.label}
+ {note.val}
+
+ ))}
+
+
+ );
+};
+
+export default NotesView;
diff --git a/app/src/views/reaction-view/NotesView.vue b/app/src/views/reaction-view/NotesView.vue
deleted file mode 100644
index 9c02e694..00000000
--- a/app/src/views/reaction-view/NotesView.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-.notes-view
- .details
- template(v-for='note in notesToDisplay')
- .label {{note.label}}
- .value {{note.val}}
-
-
-
diff --git a/app/src/views/reaction-view/ObservationsView.scss b/app/src/views/reaction-view/ObservationsView.scss
new file mode 100644
index 00000000..a729f126
--- /dev/null
+++ b/app/src/views/reaction-view/ObservationsView.scss
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.observations-view {
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+
+ .label {
+ font-weight: bold;
+ }
+ }
+}
diff --git a/app/src/views/reaction-view/ObservationsView.tsx b/app/src/views/reaction-view/ObservationsView.tsx
new file mode 100644
index 00000000..7d297180
--- /dev/null
+++ b/app/src/views/reaction-view/ObservationsView.tsx
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { formattedTime } from '../../utils/outcomes';
+import type { ReactionObservationData } from '../../types/search';
+import './ObservationsView.scss';
+
+interface ObservationsViewProps {
+ observations: ReactionObservationData[];
+}
+
+const ObservationsView: React.FC = ({ observations }) => {
+ if (!observations.length) return null;
+
+ return (
+
+
+
Time
+
Comment
+ {observations.map((obs, idx) => (
+
+ {formattedTime(obs.time) ?? ''}
+ {obs.comment}
+
+ ))}
+
+
+ );
+};
+
+export default ObservationsView;
diff --git a/app/src/views/reaction-view/ObservationsView.vue b/app/src/views/reaction-view/ObservationsView.vue
deleted file mode 100644
index 9baba981..00000000
--- a/app/src/views/reaction-view/ObservationsView.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-.notes-view
- .details
- .label Time
- .label Comment
- template(v-for='obs in observations')
- .value {{getTime(obs.time)}}
- .value {{obs.comment}}
-
-
-
diff --git a/app/src/views/reaction-view/OutcomesView.scss b/app/src/views/reaction-view/OutcomesView.scss
new file mode 100644
index 00000000..d553c968
--- /dev/null
+++ b/app/src/views/reaction-view/OutcomesView.scss
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '../../styles/vars' as vars;
+
+.outcomes-view {
+ .title {
+ font-weight: 700;
+ font-size: 1.5rem;
+ margin-bottom: 0.25rem;
+ margin-top: 1rem;
+ }
+
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+
+ > * {
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ .sub-section {
+ border: 1px solid vars.$medgrey;
+ border-radius: 0.25rem;
+ padding: 1rem;
+
+ .compound {
+ width: fit-content;
+ }
+
+ .sub-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ }
+
+ .measurements {
+ display: grid;
+ grid-template-columns: repeat(4, auto) 1fr;
+ column-gap: 0.5rem;
+ row-gap: 1rem;
+ padding: 0.5rem;
+ width: fit-content;
+ align-items: center;
+
+ .label {
+ font-weight: 700;
+ }
+ }
+
+ .raw {
+ .button {
+ padding: 0.5rem;
+ background-color: vars.$darkgrey;
+ color: white;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ width: fit-content;
+ }
+ }
+ }
+}
diff --git a/app/src/views/reaction-view/OutcomesView.tsx b/app/src/views/reaction-view/OutcomesView.tsx
new file mode 100644
index 00000000..173cfe80
--- /dev/null
+++ b/app/src/views/reaction-view/OutcomesView.tsx
@@ -0,0 +1,236 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import reaction_pb from 'ord-schema';
+import type { Analysis, ProductMeasurement } from 'ord-schema/proto/reaction_pb';
+import CompoundView from './CompoundView';
+import FloatingModal from '../../components/FloatingModal';
+import { amountObj, amountStr } from '../../utils/amount';
+import { enumName } from '../../utils/enum';
+import { formatPercentage, formattedTime } from '../../utils/outcomes';
+import type { ReactionOutcomeData } from '../../types/search';
+import './OutcomesView.scss';
+
+interface OutcomesViewProps {
+ outcome: ReactionOutcomeData | undefined;
+}
+
+const measurementType = (type: number | undefined): string =>
+ enumName(reaction_pb.ProductMeasurement.ProductMeasurementType, type) ?? '';
+
+const analysisType = (type: number | undefined): string => enumName(reaction_pb.Analysis.AnalysisType, type) ?? '';
+
+const measurementValue = (measurement: ProductMeasurement.AsObject): string => {
+ if (measurement.percentage) return formatPercentage(measurement.percentage);
+ if (measurement.amount) return amountStr(amountObj(measurement.amount));
+ if (measurement.floatValue) return String(measurement.floatValue.value);
+ if (measurement.stringValue) return measurement.stringValue;
+ return '';
+};
+
+const measurementWithNamedType = (measurement: ProductMeasurement.AsObject) => ({
+ ...measurement,
+ type: measurementType(measurement.type),
+});
+
+const analysisWithNamedType = (analysis: Analysis.AsObject) => ({
+ ...analysis,
+ type: analysisType(analysis.type),
+});
+
+const OutcomesView: React.FC = ({ outcome }) => {
+ const [productsIdx, setProductsIdx] = useState(0);
+ const [analysesIdx, setAnalysesIdx] = useState(0);
+ const [rawMeasurement, setRawMeasurement] = useState | null>(null);
+ const [rawAnalysis, setRawAnalysis] = useState | null>(null);
+ const [customDetails, setCustomDetails] = useState(null);
+
+ if (!outcome) return null;
+
+ const reactionTime = formattedTime(outcome.reactionTime);
+ const conversion = outcome.conversion;
+ const showDetails = Boolean(reactionTime || conversion);
+
+ const currentProduct = outcome.productsList?.[productsIdx];
+ const currentAnalysis = outcome.analysesMap?.[analysesIdx]?.[1];
+
+ return (
+
+ {showDetails && (
+ <>
+
Details
+
+ {reactionTime && (
+ <>
+
Reaction Time
+
{reactionTime}
+ >
+ )}
+ {conversion && (
+ <>
+
Conversion
+
{formatPercentage(conversion)}
+ >
+ )}
+
+ >
+ )}
+
+
Products
+
+
+ {outcome.productsList?.map((_product, idx) => (
+
setProductsIdx(idx)}
+ >
+ Product {idx + 1}
+
+ ))}
+
+
+ {currentProduct && (
+ <>
+
+
+
+
+
Measurements
+
+
Type
+
+
Value
+
Analysis
+
Raw
+ {currentProduct.measurementsList.map((measurement, idx) => {
+ const typeName = measurementType(measurement.type);
+ return (
+
+ {typeName === 'CUSTOM' ? (
+
+
+ setCustomDetails(
+ measurement.details ||
+ 'Please contact the author for details on this custom measurement.',
+ )
+ }
+ >
+ CUSTOM
+
+
+ ) : (
+ {typeName}
+ )}
+
+ {measurementValue(measurement)}
+ {measurement.analysisKey}
+
+
+
setRawMeasurement(measurementWithNamedType(measurement))}
+ >
+ <>
+
+
+
+
+ );
+ })}
+
+ >
+ )}
+
+ {rawMeasurement && (
+
setRawMeasurement(null)}
+ >
+
+
{JSON.stringify(rawMeasurement, null, 2)}
+
+
+ )}
+
+ {customDetails !== null && (
+
setCustomDetails(null)}
+ >
+
+
+ )}
+
+
+ {outcome.analysesMap && outcome.analysesMap.length > 0 && (
+ <>
+
Analyses
+
+
+ {outcome.analysesMap.map(([key], idx) => (
+
setAnalysesIdx(idx)}
+ >
+ {key}
+
+ ))}
+
+ {currentAnalysis && (
+
+
Type
+
{analysisType(currentAnalysis.type)}
+
Details
+
{currentAnalysis.details}
+
Raw
+
+
+
setRawAnalysis(analysisWithNamedType(currentAnalysis))}
+ >
+ <>
+
+
+
+
+ )}
+
+
+ {rawAnalysis && (
+
setRawAnalysis(null)}
+ >
+
+
{JSON.stringify(rawAnalysis, null, 2)}
+
+
+ )}
+ >
+ )}
+
+ );
+};
+
+export default OutcomesView;
diff --git a/app/src/views/reaction-view/OutcomesView.vue b/app/src/views/reaction-view/OutcomesView.vue
deleted file mode 100644
index bb20d2d8..00000000
--- a/app/src/views/reaction-view/OutcomesView.vue
+++ /dev/null
@@ -1,223 +0,0 @@
-
-
-
-
-
-.outcomes-view
- template(v-if='outcome.reactionTime || outcome.conversion')
- .title Details
- .details
- template(v-if='outcome.reactionTime')
- .label Reaction Time
- .value {{reactionTime}}
- template(v-if='outcome.conversion')
- .label Conversion
- .value {{outcome.conversion.value}} {{isNaN(outcome.conversion.precision) ? "" : `± ${outcome.conversion.precision}`}}
- .title Products
- .sub-section
- .tabs
- .tab(
- v-for='(product, idx) in outcome.productsList'
- @click='productsIdx = idx'
- :class='productsIdx === idx ? "selected" : ""'
- ) Product {{idx + 1}}
- .compound
- CompoundView(
- :component='outcome.productsList[productsIdx]'
- )
- .sub-title Measurements
- .measurements
- .label Type
- .label
- .label Value
- .label Analysis
- .label Raw
- template(v-for='measurement in outcome.productsList[productsIdx].measurementsList')
- .value(v-if='getMeasurementType(measurement.type) == CUSTOM')
- .button(@click='setCustomMeasurementDetails(measurement)') {{'CUSTOM '}}
- .value(v-else) {{getMeasurementType(measurement.type)}}
- .value
- .value {{getMeasurementValue(measurement)}}
- .value {{measurement.analysisKey}}
- .value
- .raw
- .button(@click='setRawMeasurement(measurement)') <>
- floating-modal(
- v-if='showRawMeasurement'
- title="Raw Data"
- @closeModal='showRawMeasurement=false'
- )
- .data
- pre {{rawMeasurement}}
- floating-modal(
- v-if='showCustomMeasurementDetails'
- title="Custom Measurement Details"
- @closeModal='showCustomMeasurementDetails=false'
- )
- .data
- pre {{customMeasurementDetails}}
- template(v-if='outcome.analysesMap?.length')
- .title Analyses
- .sub-section
- .tabs
- .tab(
- v-for='(analysis, idx) in outcome.analysesMap'
- @click='analysesIdx = idx'
- :class='analysesIdx === idx ? "selected" : ""'
- ) {{analysis[0]}}
- .details
- .label Type
- .value {{getAnalysisType(outcome.analysesMap[analysesIdx][1].type)}}
- .label Details
- .value {{outcome.analysesMap[analysesIdx][1].details}}
- .label Raw
- .value
- .raw
- .button(@click='setRawAnalysis(outcome.analysesMap[analysesIdx][1])') <>
- floating-modal(
- v-if='showRawAnalysis'
- title="Raw Data"
- @closeModal='showRawAnalysis=false'
- )
- .data
- pre {{rawAnalysis}}
-
-
-
-
-
diff --git a/app/src/views/reaction-view/ProvenanceView.scss b/app/src/views/reaction-view/ProvenanceView.scss
new file mode 100644
index 00000000..8a94fd2f
--- /dev/null
+++ b/app/src/views/reaction-view/ProvenanceView.scss
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.provenance-view {
+ .title {
+ font-weight: 700;
+ font-size: 1.5rem;
+ margin-bottom: 0.25rem;
+ }
+
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+
+ &.experimenter {
+ grid-template-columns: repeat(4, auto) 1fr;
+
+ .label {
+ font-weight: 700;
+ }
+ }
+ }
+}
diff --git a/app/src/views/reaction-view/ProvenanceView.tsx b/app/src/views/reaction-view/ProvenanceView.tsx
new file mode 100644
index 00000000..a2b6db56
--- /dev/null
+++ b/app/src/views/reaction-view/ProvenanceView.tsx
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import type { ReactionProvenanceData } from '../../types/search';
+import './ProvenanceView.scss';
+
+interface ProvenanceViewProps {
+ provenance: ReactionProvenanceData | undefined;
+}
+
+const ProvenanceView: React.FC = ({ provenance }) => {
+ return (
+
+ {provenance?.experimenter && (
+ <>
+
Experimenter
+
+
Username
+
Name
+
ORCID
+
Organization
+
Email
+
{provenance.experimenter.username}
+
{provenance.experimenter.name}
+
{provenance.experimenter.orcid}
+
{provenance.experimenter.organization}
+
{provenance.experimenter.email}
+
+ >
+ )}
+
+
+ {provenance?.city && (
+ <>
+
City
+
{provenance.city}
+ >
+ )}
+
+ {provenance?.experimentStart?.value && (
+ <>
+
Experiment Start
+
{new Date(provenance.experimentStart.value).toLocaleDateString()}
+ >
+ )}
+
+ {provenance?.doi && (
+ <>
+
DOI
+
{provenance.doi}
+ >
+ )}
+
+ {provenance?.patent && (
+ <>
+
Patent
+
{provenance.patent}
+ >
+ )}
+
+ {provenance?.publicationUrl && (
+ <>
+
Publication URL
+
+ >
+ )}
+
+
+ );
+};
+
+export default ProvenanceView;
diff --git a/app/src/views/reaction-view/ProvenanceView.vue b/app/src/views/reaction-view/ProvenanceView.vue
deleted file mode 100644
index 9d98b224..00000000
--- a/app/src/views/reaction-view/ProvenanceView.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-.provenance-view
- template(v-if='provenance.experimenter')
- .title Experimenter
- .details.experimenter
- .label Username
- .label Name
- .label ORCID
- .label Organization
- .label Email
- .value {{provenance.experimenter.username}}
- .value {{provenance.experimenter.name}}
- .value {{provenance.experimenter.orcid}}
- .value {{provenance.experimenter.organization}}
- .value {{provenance.experimenter.email}}
- .details
- template(v-if='provenance.city')
- .label City
- .value {{provenance.city}}
- template(v-if='provenance.experimentStart')
- .label City
- .value {{new Date(provenance.experimentStart).toLocaleDateString()}}
- template(v-if='provenance.doi')
- .label DOI
- .value {{provenance.doi}}
- template(v-if='provenance.patent')
- .label Patent
- .value {{provenance.patent}}
- template(v-if='provenance.publicationUrl')
- .label Publication URL
- .value
- a(:href='provenance.publicationUrl' target="_blank") {{provenance.publicationUrl}}
-
-
-
diff --git a/app/src/views/reaction-view/SetupView.scss b/app/src/views/reaction-view/SetupView.scss
new file mode 100644
index 00000000..c8003892
--- /dev/null
+++ b/app/src/views/reaction-view/SetupView.scss
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.setup-view {
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+ }
+}
diff --git a/app/src/views/reaction-view/SetupView.tsx b/app/src/views/reaction-view/SetupView.tsx
new file mode 100644
index 00000000..6fd85a1f
--- /dev/null
+++ b/app/src/views/reaction-view/SetupView.tsx
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useMemo } from 'react';
+import reaction_pb from 'ord-schema';
+import { enumName } from '../../utils/enum';
+import type { ReactionSetupData } from '../../types/search';
+import './SetupView.scss';
+
+interface SetupViewProps {
+ setup: ReactionSetupData | undefined;
+ display: string;
+}
+
+const SetupView: React.FC = ({ setup, display }) => {
+ const vessel = useMemo(() => setup?.vessel, [setup?.vessel]);
+
+ const vesselType = useMemo(() => {
+ if (!vessel?.type) return '';
+ return enumName(reaction_pb.Vessel.VesselType, vessel.type) ?? '';
+ }, [vessel?.type]);
+
+ const vesselVolume = useMemo(() => {
+ if (!vessel?.volume) return '';
+ const label = enumName(reaction_pb.Volume.VolumeUnit, vessel.volume.units);
+ return `${vessel.volume.value} ${label ? String(label).toLowerCase() : ''}`;
+ }, [vessel?.volume]);
+
+ const vesselAttachments = useMemo(() => {
+ if (!vessel?.attachmentsList?.length) return '';
+ return vessel.attachmentsList
+ .map(attach => {
+ const type = enumName(reaction_pb.VesselAttachment.VesselAttachmentType, attach.type);
+ return `${String(type ?? '')}${attach.details ? `: ${attach.details}` : ''}`;
+ })
+ .join(', ');
+ }, [vessel?.attachmentsList]);
+
+ const vesselPrep = useMemo(() => {
+ if (!vessel?.preparationsList?.length) return '';
+ return vessel.preparationsList
+ .map(prep => {
+ const type = enumName(reaction_pb.VesselPreparation.VesselPreparationType, prep.type);
+ return `${String(type ?? '')}${prep.details ? `: ${prep.details}` : ''}`;
+ })
+ .join(', ');
+ }, [vessel?.preparationsList]);
+
+ const vesselMaterial = useMemo(() => {
+ if (!vessel?.material?.type) return '';
+ return String(enumName(reaction_pb.VesselMaterial.VesselMaterialType, vessel.material.type) ?? '');
+ }, [vessel?.material?.type]);
+
+ const reactionEnv = useMemo(() => {
+ const envVal = setup?.environment?.type;
+ if (!envVal) return null;
+ return (
+ String(enumName(reaction_pb.ReactionSetup.ReactionEnvironment.ReactionEnvironmentType, envVal) ?? '') || null
+ );
+ }, [setup?.environment?.type]);
+
+ return (
+
+ {display === 'vessel' && (
+
+
Type
+
{vesselType}
+
+ {vessel?.details && (
+ <>
+
Details
+
{vessel.details}
+ >
+ )}
+
+
Material
+
{vesselMaterial || 'UNSPECIFIED'}
+
+
Volume
+
{vesselVolume}
+
+ {vessel?.attachmentsList && vessel.attachmentsList.length > 0 && (
+ <>
+
Attachments
+
{vesselAttachments}
+ >
+ )}
+
+ {vessel?.preparationsList && vessel.preparationsList.length > 0 && (
+ <>
+
Preparations
+
{vesselPrep}
+ >
+ )}
+
+ )}
+
+ {display === 'environment' && (
+
+
Type
+
{reactionEnv || 'UNSPECIFIED'}
+
+ {setup?.environment?.details && (
+ <>
+
Details
+
{setup.environment.details}
+ >
+ )}
+
+ )}
+
+ {display === 'automation' && (
+
+
Platform
+
{setup?.automationPlatform || 'UNSPECIFIED'}
+
+ )}
+
+ );
+};
+
+export default SetupView;
diff --git a/app/src/views/reaction-view/SetupView.vue b/app/src/views/reaction-view/SetupView.vue
deleted file mode 100644
index 6081290d..00000000
--- a/app/src/views/reaction-view/SetupView.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
-
-.setup-view
- .vessel.details(v-if='display=="vessel"')
- .label Type
- .value {{vesselType}}
- template(v-if='vessel.details')
- .label Details
- .value {{vessel.details}}
- .label Material
- .value {{vesselMaterial || "UNSPECIFIED"}}
- .label Volume
- .value {{vesselVolume}}
- template(v-if='vessel.attachmentsList?.length')
- .label Attachments
- .value {{vesselAttachments}}
- template(v-if='vessel.preparationsList?.length')
- .label Preparations
- .value {{vesselPrep}}
- .environment.details(v-if='display=="environment"')
- .label Type
- .value {{reactionEnv || "UNSPECIFIED"}}
- template(v-if='setup.environment?.details')
- .label Details
- .value {{setup.environment.details}}
- .automated.details(v-if='display=="automated"')
- .label Platform
- .value {{setup.automation_platform || "UNSPECIFIED"}}
-
-
-
diff --git a/app/src/views/reaction-view/WorkupsView.scss b/app/src/views/reaction-view/WorkupsView.scss
new file mode 100644
index 00000000..6641c3a9
--- /dev/null
+++ b/app/src/views/reaction-view/WorkupsView.scss
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.workups-view {
+ .details {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+ }
+
+ .inputs {
+ margin-top: 0.5rem;
+
+ .title {
+ font-weight: 700;
+ }
+
+ .components {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+ }
+ }
+}
diff --git a/app/src/views/reaction-view/WorkupsView.tsx b/app/src/views/reaction-view/WorkupsView.tsx
new file mode 100644
index 00000000..7bff7ee0
--- /dev/null
+++ b/app/src/views/reaction-view/WorkupsView.tsx
@@ -0,0 +1,116 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import reaction_pb from 'ord-schema';
+import type { CompoundIdentifier } from 'ord-schema/proto/reaction_pb';
+import { amountObj, amountStr } from '../../utils/amount';
+import { enumName } from '../../utils/enum';
+import { formattedTime } from '../../utils/outcomes';
+import type { ReactionWorkupData } from '../../types/search';
+import './WorkupsView.scss';
+
+interface WorkupsViewProps {
+ workup: ReactionWorkupData | undefined;
+}
+
+const NAME_IDENTIFIER_TYPE = reaction_pb.CompoundIdentifier.CompoundIdentifierType.NAME;
+
+const getNameIdentifier = (identifiers: CompoundIdentifier.AsObject[]): string => {
+ // Falls back to the first identifier when the compound has no NAME entry,
+ // rather than throwing the way the Vue source did.
+ const nameIdentifier = identifiers.find(identifier => identifier.type === NAME_IDENTIFIER_TYPE);
+ return nameIdentifier?.value ?? identifiers[0]?.value ?? '';
+};
+
+const WorkupsView: React.FC = ({ workup }) => {
+ if (!workup) return null;
+
+ const workupType = enumName(reaction_pb.ReactionWorkup.ReactionWorkupType, workup.type) ?? '';
+ const duration = formattedTime(workup.duration);
+ const aliquotAmount = workup.amount ? amountStr(amountObj(workup.amount)) : '';
+
+ return (
+
+
+
Type
+
{workupType}
+
+ {workup.details && (
+ <>
+
Details
+
{workup.details}
+ >
+ )}
+
+ {duration && (
+ <>
+
Duration
+
{duration}
+ >
+ )}
+
+ {aliquotAmount && (
+ <>
+
Aliquot amount
+
{aliquotAmount}
+ >
+ )}
+
+ {workup.keepPhase && (
+ <>
+
Phase kept
+
{workup.keepPhase}
+ >
+ )}
+
+ {/* proto3 defaults targetPh to 0 when unset, so 0 is ambiguous (default
+ vs. "actually strongly acidic"). Match the Vue v-if='workup.targetPh'
+ behavior and hide on 0; if a record ever sets it to 0 explicitly,
+ we'll need a `has*` helper from the schema to disambiguate. */}
+ {workup.targetPh !== undefined && workup.targetPh !== 0 && (
+ <>
+
Target pH
+
{workup.targetPh}
+ >
+ )}
+
+ {workup.isAutomated && (
+ <>
+
Automated
+
yes
+ >
+ )}
+
+
+ {workup.input && workup.input.componentsList.length > 0 && (
+
+
Inputs
+
+ {workup.input.componentsList.map((component, idx) => (
+
+ {getNameIdentifier(component.identifiersList)}
+ {amountStr(amountObj(component.amount))}
+
+ ))}
+
+
+ )}
+
+ );
+};
+
+export default WorkupsView;
diff --git a/app/src/views/reaction-view/WorkupsView.vue b/app/src/views/reaction-view/WorkupsView.vue
deleted file mode 100644
index ac2eb985..00000000
--- a/app/src/views/reaction-view/WorkupsView.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-.workups-view
- .details
- .label Type
- .value {{workupType}}
- template(v-if='workup.details')
- .label Details
- .value {{workup.details}}
- template(v-if='workup.duration')
- .label Duration
- .value {{workup.duration}}
- template(v-if='workup.amount')
- .label Aliquot amount
- .value {{workup.amount}}
- template(v-if='workup.keepPhase')
- .label Phase kept
- .value {{workup.keepPhase}}
- template(v-if='workup.targetPh')
- .label Target pH
- .value {{workup.targetPh}}
- template(v-if='workup.isAutomated')
- .label Automated
- .value {{workup.Automated}}
- .inputs(v-if='workup.input')
- .title Inputs
- .components
- template(v-for='component in workup.input.componentsList')
- .identifier {{getIdentifier(component.identifiersList)}}
- .amount {{getAmount(component.amount)}}
-
-
-
-
diff --git a/app/src/views/search/MainSearch.scss b/app/src/views/search/MainSearch.scss
new file mode 100644
index 00000000..f38e73ae
--- /dev/null
+++ b/app/src/views/search/MainSearch.scss
@@ -0,0 +1,99 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import '../../styles/vars';
+
+#search-main {
+ width: 95%;
+ margin: 1rem 2.5%;
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ min-width: 800px;
+
+ .search-options-container {
+ .title {
+ font-size: 2rem;
+ font-weight: 700;
+ margin-bottom: 0.85rem;
+ }
+
+ .options-holder {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 1rem;
+ background-color: white;
+ padding: 1rem;
+ box-sizing: border-box;
+ border-radius: 0.25rem;
+ overflow-y: auto;
+ }
+ }
+
+ .no-results {
+ margin-top: 1rem;
+ text-align: center;
+ }
+
+ @media (max-width: 1000px) {
+ grid-template-columns: 1fr;
+
+ .title {
+ display: none;
+ }
+
+ .search-options-container {
+ position: fixed;
+ height: 100vh;
+ width: 90%;
+ transition: 0.5s;
+ top: 0;
+
+ &.hidden {
+ left: -81%;
+ }
+
+ &.slide-out {
+ left: 0;
+ }
+
+ .options-holder {
+ width: 90%;
+ height: 100%;
+ overflow-y: auto;
+ box-shadow: 0 0 5px $darkgrey;
+ }
+
+ .slide-out-tab {
+ background-color: white;
+ position: absolute;
+ left: 89.5%;
+ width: 7.5%;
+ top: 4rem;
+ box-shadow: 6px 3px 5px #ccc;
+ padding: 0.5rem 0;
+ border-bottom-right-radius: 0.25rem;
+
+ .line {
+ background-color: black;
+ height: 2px;
+ width: 60%;
+ margin: 0.5rem auto;
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/views/search/MainSearch.tsx b/app/src/views/search/MainSearch.tsx
new file mode 100644
index 00000000..12237f1e
--- /dev/null
+++ b/app/src/views/search/MainSearch.tsx
@@ -0,0 +1,151 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useMemo } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+import SearchOptions from './SearchOptions';
+import SearchResults from './SearchResults';
+import LoadingSpinner from '../../components/LoadingSpinner';
+import { useSearchTask } from '../../hooks/useSearchTask';
+import './MainSearch.scss';
+
+interface SearchOptionsData {
+ reagent: {
+ reagents: Array<{ smileSmart: string; source: string; matchMode: string }>;
+ useStereochemistry: boolean;
+ similarityThreshold: number;
+ };
+ reaction: {
+ reactionIds: string[];
+ reactionSmarts: string[];
+ min_yield: number;
+ max_yield: number;
+ min_conversion: number;
+ max_conversion: number;
+ };
+ dataset: {
+ datasetIds: string[];
+ DOIs: string[];
+ };
+ general: {
+ limit: number;
+ };
+}
+
+const MEANINGFUL_QUERY_PARAMS = [
+ 'component',
+ 'dataset_id',
+ 'doi',
+ 'reaction_id',
+ 'reaction_smarts',
+ 'min_yield',
+ 'max_yield',
+ 'min_conversion',
+ 'max_conversion',
+];
+
+const MainSearch: React.FC = () => {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [showOptions, setShowOptions] = useState(false);
+
+ const hasSearchParams = useMemo(() => {
+ const params = new URLSearchParams(location.search);
+ return MEANINGFUL_QUERY_PARAMS.some(param => params.has(param));
+ }, [location.search]);
+
+ const { data, isFetching, error } = useSearchTask(location.search, hasSearchParams);
+ const searchResults = data?.status === 'success' ? data.results : [];
+ const loading = hasSearchParams && (isFetching || data?.status === 'pending');
+
+ const updateSearchOptions = (options: SearchOptionsData) => {
+ const searchParams = new URLSearchParams();
+
+ if (options.reagent.reagents.length) {
+ options.reagent.reagents.forEach(reagent => {
+ searchParams.append('component', `${reagent.smileSmart};${reagent.source};${reagent.matchMode.toLowerCase()}`);
+ });
+ searchParams.set('use_stereochemistry', options.reagent.useStereochemistry.toString());
+ searchParams.set('similarity', options.reagent.similarityThreshold.toString());
+ }
+
+ options.dataset.datasetIds.forEach(id => searchParams.append('dataset_id', id));
+ options.dataset.DOIs.forEach(doi => searchParams.append('doi', doi));
+ options.reaction.reactionIds.forEach(id => searchParams.append('reaction_id', id));
+ options.reaction.reactionSmarts.forEach(smarts => searchParams.append('reaction_smarts', smarts));
+
+ // Yield/conversion: only include when narrower than the full 0-100 range.
+ if (options.reaction.min_yield !== 0 || options.reaction.max_yield !== 100) {
+ searchParams.set('min_yield', options.reaction.min_yield.toString());
+ searchParams.set('max_yield', options.reaction.max_yield.toString());
+ }
+ if (options.reaction.min_conversion !== 0 || options.reaction.max_conversion !== 100) {
+ searchParams.set('min_conversion', options.reaction.min_conversion.toString());
+ searchParams.set('max_conversion', options.reaction.max_conversion.toString());
+ }
+
+ searchParams.set('limit', options.general.limit.toString() || '100');
+
+ navigate({ pathname: '/search', search: searchParams.toString() });
+ };
+
+ return (
+
+
+
Filters & Options
+
+
+
+
setShowOptions(!showOptions)}
+ >
+
+
+
+
+
+
+
+ {!hasSearchParams && (
+
+
Enter search criteria using the filters and options panel, then click Search.
+
+ )}
+ {hasSearchParams && error && (
+
+
Search failed: {error.message}
+
+ )}
+ {hasSearchParams && !error && !loading && searchResults.length > 0 && (
+
+ )}
+ {hasSearchParams && !error && !loading && !searchResults.length && (
+
+
No results. Adjust the filters and options and search again.
+
+ )}
+ {hasSearchParams && !error && loading && (
+
+
+
+ )}
+
+
+ );
+};
+
+export default MainSearch;
diff --git a/app/src/views/search/MainSearch.vue b/app/src/views/search/MainSearch.vue
deleted file mode 100644
index d4638bdc..00000000
--- a/app/src/views/search/MainSearch.vue
+++ /dev/null
@@ -1,286 +0,0 @@
-
-
-
-
-
-#search-main
- .search-options-container(:class='showOptions ? "slide-out" : "hidden"')
- .title Filters & Options
- .options-holder
- SearchOptions(
- @searchOptions='updateSearchOptions'
- )
- .slide-out-tab(@click='showOptions=!showOptions')
- .line
- .line
- .line
- .search-results
- SearchResults(
- :searchResults='searchResults'
- v-if='!loading && searchResults?.length'
- )
- .no-results(v-else-if='!loading && !searchResults?.length && !urlQuery')
- .title Configure the filters on the left to search the database.
- .no-results(v-else-if='!loading && !searchResults?.length')
- .title No results. Adjust the filters and options and search again.
- .loading(v-else)
- LoadingSpinner
-
-
-
-
diff --git a/app/src/views/search/SearchItemList.scss b/app/src/views/search/SearchItemList.scss
new file mode 100644
index 00000000..1f7e6259
--- /dev/null
+++ b/app/src/views/search/SearchItemList.scss
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.item-list {
+ .title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ }
+
+ .list {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 0.5rem;
+ row-gap: 0.5rem;
+ margin-bottom: 1rem;
+ }
+
+ .input {
+ display: flex;
+
+ button {
+ display: flex;
+ align-items: center;
+ margin-left: 0.5rem;
+
+ i {
+ font-size: 1.1rem;
+ }
+ }
+ }
+}
diff --git a/app/src/views/search/SearchItemList.tsx b/app/src/views/search/SearchItemList.tsx
new file mode 100644
index 00000000..0c2dcd81
--- /dev/null
+++ b/app/src/views/search/SearchItemList.tsx
@@ -0,0 +1,87 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import './SearchItemList.scss';
+
+interface SearchItemListProps {
+ title: string;
+ itemList: string[];
+ onUpdateItemList: (newList: string[]) => void;
+}
+
+const SearchItemList: React.FC = ({ title, itemList, onUpdateItemList }) => {
+ const [mutatedList, setMutatedList] = useState([]);
+ const [itemToAdd, setItemToAdd] = useState('');
+
+ const updateList = () => {
+ const newList = [...mutatedList, itemToAdd];
+ setMutatedList(newList);
+ setItemToAdd('');
+ onUpdateItemList(newList);
+ };
+
+ const deleteItem = (idx: number) => {
+ const newList = mutatedList.filter((_, index) => index !== idx);
+ setMutatedList(newList);
+ onUpdateItemList(newList);
+ };
+
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && itemToAdd.trim()) {
+ updateList();
+ }
+ };
+
+ useEffect(() => {
+ setMutatedList(itemList || []);
+ }, [itemList]);
+
+ return (
+
+
{title}
+
+ {mutatedList.map((item, idx) => (
+
+ {item}
+
+ deleteItem(idx)}>
+ delete
+
+
+
+ ))}
+
+
+ setItemToAdd(e.target.value)}
+ onKeyPress={handleKeyPress}
+ />
+
+ add
+ Add
+
+
+
+ );
+};
+
+export default SearchItemList;
diff --git a/app/src/views/search/SearchItemList.vue b/app/src/views/search/SearchItemList.vue
deleted file mode 100644
index b34ff8b9..00000000
--- a/app/src/views/search/SearchItemList.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-.item-list
- .title {{title}}
- .list
- template(v-for='(item, idx) in mutatedList')
- .text {{item}}
- .delete
- button(@click='deleteItem(idx)')
- i.material-icons delete
- .input
- input(
- type="text"
- v-model='itemToAdd'
- )
- button(@click='updateList')
- i.material-icons add
- span Add
-
-
-
diff --git a/app/src/views/search/SearchOptions.scss b/app/src/views/search/SearchOptions.scss
new file mode 100644
index 00000000..8449014e
--- /dev/null
+++ b/app/src/views/search/SearchOptions.scss
@@ -0,0 +1,205 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import '../../styles/vars';
+@import '../../styles/transition';
+@import '../../styles/tabs';
+
+.search-options {
+ max-height: 90vh;
+
+ .options-title {
+ font-size: 1.5rem;
+ font-weight: 700;
+ cursor: pointer;
+ padding: 0.5rem 1rem;
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+ margin-top: 1rem;
+ color: white;
+ background-color: $linkblue;
+ display: flex;
+ align-content: center;
+ justify-content: space-between;
+ width: 100%;
+ box-sizing: border-box;
+ transition: 0.25s;
+
+ i {
+ font-size: 2rem;
+ transition: 0.25s;
+ }
+
+ &.closed {
+ border-radius: 0.25rem;
+
+ i {
+ transform: rotate(180deg);
+ }
+ }
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ searchParameters {
+ cursor: default;
+ }
+ }
+
+ .options-container {
+ background-color: white;
+ border-bottom-left-radius: 0.25rem;
+ border-bottom-right-radius: 0.25rem;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ display: grid;
+ row-gap: 1rem;
+ border: 1px solid $medgrey;
+
+ .subtitle {
+ font-size: 1.25rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+ }
+
+ .options {
+ margin-left: 1rem;
+ }
+
+ button {
+ display: flex;
+ align-items: center;
+
+ i {
+ font-size: 1.1rem;
+ }
+ }
+
+ input,
+ select {
+ font-size: 1rem;
+ }
+
+ .search-button {
+ grid-column: 1 / 2;
+ margin-top: 1rem;
+
+ button {
+ font-size: 1.2rem;
+ padding: 0.5rem 1rem;
+ }
+ }
+ }
+
+ #searchByReagent {
+ .reagent.options {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+ align-items: center;
+
+ .label {
+ font-size: 1.1rem;
+ }
+
+ #add-component,
+ .copy {
+ grid-column: 1 / 4;
+ }
+
+ .field.long {
+ min-width: 250px;
+
+ input {
+ width: 95%;
+ }
+ }
+ }
+
+ .general.options {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: 1rem;
+ row-gap: 0.5rem;
+
+ input[type='checkbox'] {
+ max-width: 15px;
+ text-align: center;
+ }
+
+ .slider-input {
+ display: flex;
+ column-gap: 0.5rem;
+
+ #similarity {
+ max-width: 8rem;
+ }
+ }
+ }
+ }
+
+ #searchByReaction {
+ .slider-input {
+ display: grid;
+ grid-template-columns: 10rem 2.5rem 1fr;
+ column-gap: 0.5rem;
+
+ .value {
+ text-align: right;
+ }
+
+ &.multi {
+ grid-template-columns: 4rem 6rem 1fr;
+ align-items: center;
+
+ .range-container {
+ display: flex;
+ align-items: center;
+ padding: 10px 0;
+
+ /* Ensure the range slider takes full width */
+ > div {
+ width: 100%;
+ }
+
+ .range-track {
+ height: 6px;
+ width: 100%;
+ background-color: #ccc;
+ border-radius: 3px;
+ }
+
+ .range-thumb {
+ height: 20px;
+ width: 20px;
+ background-color: $linkblue;
+ border-radius: 50%;
+ border: 2px solid white;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
+ cursor: pointer;
+
+ &:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/views/search/SearchOptions.tsx b/app/src/views/search/SearchOptions.tsx
new file mode 100644
index 00000000..6e50d794
--- /dev/null
+++ b/app/src/views/search/SearchOptions.tsx
@@ -0,0 +1,612 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import { useLocation } from 'react-router-dom';
+import { Range } from 'react-range';
+import ModalKetcher from '../../components/ModalKetcher';
+import SearchItemList from './SearchItemList';
+import './SearchOptions.scss';
+
+interface ReagentComponent {
+ smileSmart: string;
+ source: string;
+ matchMode?: string;
+}
+
+interface ReagentOptions {
+ reactants: ReagentComponent[];
+ products: ReagentComponent[];
+ matchMode: string;
+ useStereochemistry: boolean;
+ similarityThreshold: number;
+}
+
+interface ReactionOptions {
+ reactionIds: string[];
+ reactionSmarts: string[];
+ min_yield: number;
+ max_yield: number;
+ min_conversion: number;
+ max_conversion: number;
+}
+
+interface DatasetOptions {
+ datasetIds: string[];
+ DOIs: string[];
+}
+
+interface SearchParams {
+ limit: number;
+}
+
+interface SearchOptionsData {
+ reagent: {
+ reagents: Array<{ smileSmart: string; source: string; matchMode: string }>;
+ useStereochemistry: boolean;
+ similarityThreshold: number;
+ };
+ reaction: ReactionOptions;
+ dataset: DatasetOptions;
+ general: SearchParams;
+}
+
+interface SearchOptionsProps {
+ onSearchOptions: (options: SearchOptionsData) => void;
+}
+
+const SearchOptions: React.FC = ({ onSearchOptions }) => {
+ const location = useLocation();
+ const [showReagentOptions, setShowReagentOptions] = useState(false);
+ const [showReactionOptions, setShowReactionOptions] = useState(false);
+ const [showDatasetOptions, setShowDatasetOptions] = useState(false);
+
+ const [reagentOptions, setReagentOptions] = useState({
+ reactants: [],
+ products: [],
+ matchMode: 'exact',
+ useStereochemistry: false,
+ similarityThreshold: 0.5,
+ });
+
+ const [reactionOptions, setReactionOptions] = useState({
+ reactionIds: [],
+ reactionSmarts: [],
+ min_yield: 50,
+ max_yield: 100,
+ min_conversion: 50,
+ max_conversion: 100,
+ });
+
+ const [datasetOptions, setDatasetOptions] = useState({
+ datasetIds: [],
+ DOIs: [],
+ });
+
+ const [searchParams, setSearchParams] = useState({
+ limit: 100,
+ });
+
+ const [showKetcherModal, setShowKetcherModal] = useState(false);
+ const [ketcherModalSmile, setKetcherModalSmile] = useState(0);
+ const [ketcherModalSet, setKetcherModalSet] = useState<'reactants' | 'products'>('reactants');
+
+ const matchModes = ['exact', 'similar', 'substructure', 'SMARTS'];
+
+ const defaultQuery = useMemo>(() => {
+ const params = new URLSearchParams(location.search);
+ const query: Record = {};
+ for (const [key, value] of params.entries()) {
+ const existing = query[key];
+ if (existing === undefined) {
+ query[key] = value;
+ } else if (Array.isArray(existing)) {
+ existing.push(value);
+ } else {
+ query[key] = [existing, value];
+ }
+ }
+ return query;
+ }, [location.search]);
+
+ const simThresholdDisplay = useMemo(() => {
+ let trailingZeros = '';
+ const simThresh = reagentOptions.similarityThreshold.toString();
+ if (simThresh?.length < 2) trailingZeros = '.00';
+ else if (simThresh?.length < 4) trailingZeros = '0';
+ return simThresh + trailingZeros;
+ }, [reagentOptions.similarityThreshold]);
+
+ const openKetcherModal = (componentSet: 'reactants' | 'products', idx: number) => {
+ setKetcherModalSmile(idx);
+ setKetcherModalSet(componentSet);
+ setShowKetcherModal(true);
+ };
+
+ const emitSearchOptions = () => {
+ const allComponents = [...reagentOptions.reactants, ...reagentOptions.products].map(component => ({
+ ...component,
+ matchMode: reagentOptions.matchMode,
+ }));
+
+ const searchOptions: SearchOptionsData = {
+ reagent: {
+ reagents: allComponents,
+ useStereochemistry: reagentOptions.useStereochemistry,
+ similarityThreshold: reagentOptions.similarityThreshold,
+ },
+ reaction: reactionOptions,
+ dataset: datasetOptions,
+ general: searchParams,
+ };
+
+ onSearchOptions(searchOptions);
+ };
+
+ const setDefaultValues = useCallback(() => {
+ const q = defaultQuery;
+
+ // Reset reagent options first, then rebuild from URL
+ const initialReagentOptions: ReagentOptions = {
+ reactants: [],
+ products: [],
+ matchMode: 'exact',
+ useStereochemistry: false,
+ similarityThreshold: 0.5,
+ };
+
+ // reagent options
+ if (q.component?.length) {
+ const components = Array.isArray(q.component) ? q.component : [q.component];
+
+ components.forEach((comp: string) => {
+ const compArray = comp.split(';');
+ const compType = compArray[1] === 'input' ? 'reactants' : 'products';
+ const matchMode = compArray[2] || 'exact';
+ const component: ReagentComponent = {
+ smileSmart: compArray[0].replaceAll('%3D', '='),
+ source: compArray[1],
+ matchMode,
+ };
+
+ initialReagentOptions[compType].push(component);
+ // Use the last matchMode found (or first if none specified)
+ if (matchMode) {
+ initialReagentOptions.matchMode = matchMode;
+ }
+ });
+
+ initialReagentOptions.useStereochemistry = q.use_stereochemistry === 'true' || false;
+ initialReagentOptions.similarityThreshold = Number(q.similarity) || 0.5;
+
+ setReagentOptions(initialReagentOptions);
+ setShowReagentOptions(true);
+ } else {
+ // If no components in URL, reset to defaults
+ setReagentOptions(initialReagentOptions);
+ }
+
+ // dataset options
+ const datasetIds = Array.isArray(q.dataset_id) ? q.dataset_id : q.dataset_id?.length ? [q.dataset_id] : [];
+ const DOIs = Array.isArray(q.doi) ? q.doi : q.doi?.length ? [q.doi] : [];
+
+ setDatasetOptions({ datasetIds, DOIs });
+ if (datasetIds.length || DOIs.length) {
+ setShowDatasetOptions(true);
+ }
+
+ // reaction options
+ const reactionIds = Array.isArray(q.reaction_id) ? q.reaction_id : q.reaction_id?.length ? [q.reaction_id] : [];
+ const reactionSmarts = Array.isArray(q.reaction_smarts)
+ ? q.reaction_smarts
+ : q.reaction_smarts?.length
+ ? [q.reaction_smarts]
+ : [];
+
+ setReactionOptions(prev => ({
+ ...prev,
+ reactionIds,
+ reactionSmarts,
+ min_yield: Number(q.min_yield) || 0,
+ max_yield: Number(q.max_yield) || 100,
+ min_conversion: Number(q.min_conversion) || 0,
+ max_conversion: Number(q.max_conversion) || 100,
+ }));
+
+ // Number(q.max_yield) !== 100 used to trip on every page load because
+ // Number(undefined) is NaN and NaN !== 100 is true; gate each bound on
+ // its URL param being present before comparing.
+ if (
+ reactionIds.length ||
+ reactionSmarts.length ||
+ q.min_yield !== undefined ||
+ (q.max_yield !== undefined && Number(q.max_yield) !== 100) ||
+ q.min_conversion !== undefined ||
+ (q.max_conversion !== undefined && Number(q.max_conversion) !== 100)
+ ) {
+ setShowReactionOptions(true);
+ }
+
+ // general search params
+ setSearchParams({ limit: Number(q.limit) || 100 });
+ }, [defaultQuery]);
+
+ const addReactant = () => {
+ setReagentOptions(prev => ({
+ ...prev,
+ reactants: [...prev.reactants, { smileSmart: '', source: 'input' }],
+ }));
+ };
+
+ const removeReactant = (idx: number) => {
+ setReagentOptions(prev => ({
+ ...prev,
+ reactants: prev.reactants.filter((_, index) => index !== idx),
+ }));
+ };
+
+ const addProduct = () => {
+ setReagentOptions(prev => ({
+ ...prev,
+ products: [...prev.products, { smileSmart: '', source: 'output' }],
+ }));
+ };
+
+ const removeProduct = (idx: number) => {
+ setReagentOptions(prev => ({
+ ...prev,
+ products: prev.products.filter((_, index) => index !== idx),
+ }));
+ };
+
+ const updateReactantSmiles = (idx: number, smiles: string) => {
+ setReagentOptions(prev => ({
+ ...prev,
+ reactants: prev.reactants.map((reactant, index) =>
+ index === idx ? { ...reactant, smileSmart: smiles } : reactant,
+ ),
+ }));
+ };
+
+ const updateProductSmiles = (idx: number, smiles: string) => {
+ setReagentOptions(prev => ({
+ ...prev,
+ products: prev.products.map((product, index) => (index === idx ? { ...product, smileSmart: smiles } : product)),
+ }));
+ };
+
+ const updateKetcherSmiles = (newSmiles: string) => {
+ if (ketcherModalSet === 'reactants') {
+ updateReactantSmiles(ketcherModalSmile, newSmiles);
+ } else {
+ updateProductSmiles(ketcherModalSmile, newSmiles);
+ }
+ };
+
+ useEffect(() => {
+ setDefaultValues();
+ }, [setDefaultValues]);
+
+ return (
+
+ {/* Components Section */}
+
setShowReagentOptions(!showReagentOptions)}
+ >
+ Components
+ expand_less
+
+
+ {showReagentOptions && (
+
+
+
+
+ {matchModes.map(mode => (
+
setReagentOptions(prev => ({ ...prev, matchMode: mode }))}
+ >
+ {mode}
+
+ ))}
+
+
+
+
+
+
General Options
+
+
Use Stereochemistry
+
setReagentOptions(prev => ({ ...prev, useStereochemistry: e.target.checked }))}
+ />
+ {reagentOptions.matchMode === 'similar' && (
+ <>
+
Similarity Threshold
+
+
{simThresholdDisplay}
+
+ setReagentOptions(prev => ({ ...prev, similarityThreshold: Number(e.target.value) }))
+ }
+ />
+
+ >
+ )}
+
+
+
+
+
Reactants & Reagents
+
+ {reagentOptions.reactants.map((reactant, idx) => (
+
+
+ openKetcherModal('reactants', idx)}>
+ draw
+
+
+
+ updateReactantSmiles(idx, e.target.value)}
+ />
+
+
+ removeReactant(idx)}>
+ delete
+
+
+
+ ))}
+ {!reagentOptions.reactants?.length &&
No components
}
+
+
+ add
+ Add Component
+
+
+
+
+
+
+
Products
+
+ {reagentOptions.products.map((product, idx) => (
+
+
+ openKetcherModal('products', idx)}>
+ draw
+
+
+
+ updateProductSmiles(idx, e.target.value)}
+ />
+
+
+ removeProduct(idx)}>
+ delete
+
+
+
+ ))}
+ {!reagentOptions.products?.length &&
No components
}
+
+
+ add
+ Add Component
+
+
+
+
+
+ )}
+
+ {/* Reactions Section */}
+
setShowReactionOptions(!showReactionOptions)}
+ >
+ Reactions
+ expand_less
+
+
+ {showReactionOptions && (
+
+
setReactionOptions(prev => ({ ...prev, reactionIds: newList }))}
+ />
+ setReactionOptions(prev => ({ ...prev, reactionSmarts: newList }))}
+ />
+
+
+
Yield
+
+ {reactionOptions.min_yield}% - {reactionOptions.max_yield}%
+
+
+
+ setReactionOptions(prev => ({
+ ...prev,
+ min_yield: values[0],
+ max_yield: values[1],
+ }))
+ }
+ renderTrack={({ props, children }) => (
+
+ {children}
+
+ )}
+ renderThumb={({ props }) => (
+
+ )}
+ />
+
+
+
+
+
Conversion
+
+ {reactionOptions.min_conversion}% - {reactionOptions.max_conversion}%
+
+
+
+ setReactionOptions(prev => ({
+ ...prev,
+ min_conversion: values[0],
+ max_conversion: values[1],
+ }))
+ }
+ renderTrack={({ props, children }) => (
+
+ {children}
+
+ )}
+ renderThumb={({ props }) => (
+
+ )}
+ />
+
+
+
+ )}
+
+ {/* Datasets Section */}
+
setShowDatasetOptions(!showDatasetOptions)}
+ >
+ Datasets
+ expand_less
+
+
+ {showDatasetOptions && (
+
+ setDatasetOptions(prev => ({ ...prev, datasetIds: newList }))}
+ />
+ setDatasetOptions(prev => ({ ...prev, DOIs: newList }))}
+ />
+
+ )}
+
+ {/* Search Parameters Section */}
+
+ Search Parameters
+
+
+
+ Result Limit
+ setSearchParams(prev => ({ ...prev, limit: Number(e.target.value) }))}
+ />
+
+
+
+ Search
+
+
+
+
+ {/* Ketcher Modal */}
+ {showKetcherModal && (
+
setShowKetcherModal(false)}
+ />
+ )}
+
+ );
+};
+
+export default SearchOptions;
diff --git a/app/src/views/search/SearchOptions.vue b/app/src/views/search/SearchOptions.vue
deleted file mode 100644
index 97f2ff0a..00000000
--- a/app/src/views/search/SearchOptions.vue
+++ /dev/null
@@ -1,439 +0,0 @@
-
-
-
-
-
-.search-options
- .options-title(
- @click='showReagentOptions = !showReagentOptions'
- :class='showReagentOptions ? "" : "closed"'
- )
- span Components
- i.material-icons expand_less
- transition(name="expand")
- #searchByReagent.options-container(
- v-if='showReagentOptions'
- )
- .section
- .subtitle
- .tabs
- .tab.capitalize(
- v-for='mode in matchModes'
- @click='reagentOptions.matchMode = mode'
- :class='reagentOptions.matchMode === mode ? "selected" : ""'
- ) {{mode}}
- .section
- .subtitle General Options
- .general.options
- label(for='stereo') Use Stereochemistry
- input#stereo(
- type='checkbox'
- v-model='reagentOptions.useStereochemistry'
- )
- template(v-if='reagentOptions.matchMode === "similar"')
- label(for='similarity') Similarity Threshold
- .slider-input
- .value {{simThresholdDisplay}}
- input#similarity(
- type='range'
- min="0.1"
- max="1.0"
- step="0.01"
- v-model='reagentOptions.similarityThreshold'
- )
- .section
- .subtitle Reactants & Reagents
- .reagent.options
- template(v-for='(reactant, idx) in reagentOptions.reactants')
- .draw
- button(@click='openKetcherModal("reactants", idx)')
- i.material-icons draw
- .field.long
- input(
- type='text'
- v-model='reactant.smileSmart'
- )
- .delete
- button(@click='reagentOptions.reactants.splice(idx,1)')
- i.material-icons delete
- .copy(v-if='!reagentOptions.reactants?.length') No components
- #add-component
- button(
- type='button'
- @click='this.reagentOptions.reactants.push({smileSmart: "", source: "input"})'
- )
- i.material-icons add
- | Add Component
- .section
- .subtitle Products
- .reagent.options
- template(v-for='(product, idx) in reagentOptions.products')
- .draw
- button(@click='openKetcherModal("products", idx)')
- i.material-icons draw
- .field.long
- input(
- type='text'
- v-model='product.smileSmart'
- )
- .delete
- button(@click='reagentOptions.products.splice(idx,1)')
- i.material-icons delete
- .copy(v-if='!reagentOptions.products?.length') No components
- #add-component
- button(
- type='button'
- @click='this.reagentOptions.products.push({smileSmart: "", source: "output"})'
- )
- i.material-icons add
- | Add Component
- .options-title(
- @click='showReactionOptions = !showReactionOptions'
- :class='showReactionOptions ? "" : "closed"'
- )
- span Reactions
- i.material-icons expand_less
- transition(name="expand")
- #searchByReaction.options-container(
- v-if='showReactionOptions'
- )
- SearchItemList(
- title='Reaction IDs'
- :itemList.sync='reactionOptions.reactionIds'
- )
- SearchItemList(
- title='Reaction SMARTS'
- :itemList.sync='reactionOptions.reactionSmarts'
- )
- .slider-input.multi
- label(for='yield') Yield
- .value {{reactionOptions.min_yield}}% - {{reactionOptions.max_yield}}%
- MultiRangeSlider(
- baseClassName="multi-range-slider"
- :miin='0'
- :max='100'
- :step='1'
- :ruler='false'
- :label='false'
- :minValue='reactionOptions.min_yield'
- :maxValue='reactionOptions.max_yield'
- @input='updateYield'
- )
- .slider-input.multi
- label(for='yield') Conversion
- .value {{reactionOptions.min_conversion}}% - {{reactionOptions.max_conversion}}%
- MultiRangeSlider(
- baseClassName="multi-range-slider"
- :miin='0'
- :max='100'
- :step='1'
- :ruler='false'
- :label='false'
- :minValue='reactionOptions.min_conversion'
- :maxValue='reactionOptions.max_conversion'
- @input='updateConversion'
- )
- .options-title(
- @click='showDatasetOptions = !showDatasetOptions'
- :class='showDatasetOptions ? "" : "closed"'
- )
- span Datasets
- i.material-icons expand_less
- transition(name="expand")
- #searchByDataset.options-container(
- v-if='showDatasetOptions'
- )
- SearchItemList(
- title='Dataset IDs'
- :itemList.sync='datasetOptions.datasetIds'
- )
- SearchItemList(
- title='DOIs'
- :itemList.sync='datasetOptions.DOIs'
- )
- #searchParameters.options-title Search Parameters
- .options-container
- .section
- label(for='limit') Result Limit
- input#limit(
- type='number'
- min='0'
- v-model='searchParams.limit'
- )
- .search-button
- button(
- @click='emitSearchOptions'
- )
- b Search
-ModalKetcher(
- v-if='showKetcherModal'
- :smiles='reagentOptions[ketcherModalSet][ketcherModalSmile].smileSmart'
- @updateSmiles='(newSmiles) => reagentOptions[ketcherModalSet][ketcherModalSmile].smileSmart = newSmiles'
- @closeModal='showKetcherModal = false'
-)
-
-
-
diff --git a/app/src/views/search/SearchResults.scss b/app/src/views/search/SearchResults.scss
new file mode 100644
index 00000000..57b0f427
--- /dev/null
+++ b/app/src/views/search/SearchResults.scss
@@ -0,0 +1,126 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import '../../styles/vars';
+@import '../../styles/transition';
+
+.search-results-main {
+ .action-button-holder {
+ margin: -2.5rem 0 1rem; // bring button row inline with title without having to pass too much into EntityTable
+ display: flex;
+ justify-content: flex-end;
+ column-gap: 0.5rem;
+ }
+
+ .reaction-container {
+ text-decoration: none;
+
+ .row {
+ background-color: white;
+ border-radius: 0.25rem;
+ padding: 0.75rem;
+ margin-bottom: 1rem;
+ transition: 0.25s;
+ border: 4px solid white;
+
+ &:hover {
+ box-shadow: 0 0 5px $darkgrey;
+ }
+
+ .select {
+ input,
+ label {
+ cursor: pointer;
+ }
+ }
+
+ .reaction-table {
+ color: black;
+ overflow-x: wrap;
+ }
+
+ .info {
+ display: grid;
+ grid-template-columns: repeat(2, 50%);
+ row-gap: 0.5rem;
+ column-gap: 1rem;
+ margin-top: 1rem;
+
+ .col.full {
+ grid-column: 1/3;
+
+ button {
+ font-size: 1.2rem;
+ }
+ }
+
+ .col {
+ * {
+ margin-top: 0.5rem;
+ }
+ }
+
+ .smile {
+ display: flex;
+ column-gap: 0.5rem;
+ align-items: center;
+ margin-top: 0.25rem;
+
+ .value {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+
+ &.selected {
+ border-color: $linkblue;
+ }
+ }
+ }
+
+ .view-selected-container {
+ position: fixed;
+ bottom: 2rem;
+ right: 6.5rem;
+
+ .view-selected-button {
+ padding: 0.5rem 1rem;
+ color: white;
+ background-color: $linkblue;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ box-shadow: 0 0 5px $darkgrey;
+ animation: bounce 5s infinite ease-in-out;
+
+ @keyframes bounce {
+ 0%,
+ 20% {
+ transform: translateY(0);
+ }
+ 10% {
+ transform: translateY(-10px);
+ }
+ }
+ }
+ }
+
+ @media (max-width: 1000px) {
+ margin-top: 2.5rem;
+ }
+}
diff --git a/app/src/views/search/SearchResults.tsx b/app/src/views/search/SearchResults.tsx
new file mode 100644
index 00000000..8043689b
--- /dev/null
+++ b/app/src/views/search/SearchResults.tsx
@@ -0,0 +1,132 @@
+/**
+ * Copyright 2023 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import EntityTable from '../../components/EntityTable';
+import ReactionCard from '../../components/ReactionCard';
+import CopyButton from '../../components/CopyButton';
+import DownloadResults from '../../components/DownloadResults';
+import type { SearchResult } from '../../types/search';
+import './SearchResults.scss';
+
+interface SearchResultsProps {
+ searchResults: SearchResult[];
+}
+
+const SearchResults: React.FC = ({ searchResults }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const [selectedReactions, setSelectedReactions] = useState([]);
+ const [showDownloadResults, setShowDownloadResults] = useState(false);
+
+ const updateSelectedReactions = (reactionId: string, isSelected: boolean) => {
+ if (isSelected) {
+ setSelectedReactions(prev => [...prev, reactionId]);
+ } else {
+ setSelectedReactions(prev => prev.filter(id => id !== reactionId));
+ }
+ };
+
+ const goToViewSelected = () => {
+ // Persist selection so it survives navigating back from the selected-set page.
+ localStorage.setItem(
+ 'storedSet',
+ JSON.stringify({
+ query: location.search,
+ reactions: selectedReactions,
+ }),
+ );
+
+ const params = new URLSearchParams();
+ selectedReactions.forEach(id => params.append('reaction_id', id));
+ navigate(`/selected-set?${params.toString()}`);
+ };
+
+ // Restore the prior selection when the user returns from /selected-set to the
+ // same search URL. Driven by location.search only; results render directly
+ // from the prop to avoid a one-frame empty flash on mount.
+ useEffect(() => {
+ const storedSetStr = localStorage.getItem('storedSet');
+ if (!storedSetStr) return;
+ try {
+ const storedSet = JSON.parse(storedSetStr);
+ if (location.search === storedSet.query) {
+ setSelectedReactions(storedSet.reactions || []);
+ }
+ } catch (error) {
+ console.error('Error parsing stored set:', error);
+ }
+ }, [location.search]);
+
+ return (
+
+ {searchResults.length > 0 && (
+
+ {entities => (
+ <>
+
+
+ setShowDownloadResults(true)}
+ >
+ Download All Search Results
+
+
+ {entities.map(row => (
+
+ ))}
+ >
+ )}
+
+ )}
+
+ {selectedReactions.length > 0 && (
+
+
+ View {selectedReactions.length} selected reactions
+
+
+ )}
+
+
result.reaction_id)}
+ showDownloadResults={showDownloadResults}
+ onHideDownloadResults={() => setShowDownloadResults(false)}
+ />
+
+ );
+};
+
+export default SearchResults;
diff --git a/app/src/views/search/SearchResults.vue b/app/src/views/search/SearchResults.vue
deleted file mode 100644
index 3b161868..00000000
--- a/app/src/views/search/SearchResults.vue
+++ /dev/null
@@ -1,182 +0,0 @@
-
-
-
-
-
-.search-results-main
- EntityTable(
- :tableData='formattedResults'
- title="Search Results",
- v-slot='{ entities }'
- v-if='formattedResults.length'
- :displaySearch='false'
- )
- .action-button-holder
- CopyButton(
- :textToCopy='fullUrl'
- icon='share'
- buttonText='Shareable Link'
- )
- button(
- :disabled='!formattedResults.length'
- @click='showDownloadResults=true'
- ) Download All Search Results
- ReactionCard(
- v-for='row in entities'
- :reaction='row'
- :isSelectable='true'
- :isSelected='selectedReactions.includes(row.reaction_id)'
- @clickedSelect='updateSelectedReactions'
- )
- transition(
- name='fade'
- )
- .view-selected-container(v-if='selectedReactions.length')
- .view-selected-button(@click='goToViewSelected') View {{selectedReactions.length}} selected reactions
- DownloadResults(
- :reactionIds='formattedResults.map(result => result.reaction_id)'
- :showDownloadResults='showDownloadResults'
- @hideDownloadResults='showDownloadResults=false'
- )
-
-
-
diff --git a/app/src/views/viewKetcher/MainKetcher.vue b/app/src/views/viewKetcher/MainKetcher.vue
deleted file mode 100644
index 03a58db1..00000000
--- a/app/src/views/viewKetcher/MainKetcher.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-//- This is a simple view which emulates the html file and pulls in the js and css files for the ketcher interface
-//- It expects a folder "ketcher" to be in the root of src with files extracted from:
-//- https://github.com/epam/ketcher/releases/download/v2.5.1/ketcher-standalone-2.5.1.zip
-#ketcher
- noscript You need to enable JavaScript to run this app.
- #root
-
-
-
-
-
-
diff --git a/app/tsconfig.app.json b/app/tsconfig.app.json
new file mode 100644
index 00000000..a9b5a59c
--- /dev/null
+++ b/app/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/app/tsconfig.json b/app/tsconfig.json
new file mode 100644
index 00000000..d32ff682
--- /dev/null
+++ b/app/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
+}
diff --git a/app/tsconfig.node.json b/app/tsconfig.node.json
new file mode 100644
index 00000000..7a77bab3
--- /dev/null
+++ b/app/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": [],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/app/vite.config.js b/app/vite.config.js
deleted file mode 100644
index 64394059..00000000
--- a/app/vite.config.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Copyright 2026 Open Reaction Database Project Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import path from 'node:path';
-import { fileURLToPath } from 'node:url';
-
-import vue from '@vitejs/plugin-vue';
-import { defineConfig } from 'vite';
-import { viteStaticCopy } from 'vite-plugin-static-copy';
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-
-export default defineConfig({
- plugins: [
- vue(),
- // Ketcher's chemical templates ship inside src/ketcher/templates and must
- // end up at /templates in the build output (the Ketcher JS bundle fetches
- // them at runtime). Ketcher v2.5.1 is otherwise self-contained — its main
- // JS bundles all assets except an `overlay.svg` referenced via a relative
- // url() that Vite resolves at build time. If Ketcher is ever updated to a
- // chunked build, the copy targets here will need to grow.
- viteStaticCopy({
- targets: [
- {
- src: 'src/ketcher/templates/*',
- dest: 'templates',
- },
- ],
- }),
- ],
- resolve: {
- alias: {
- '@': path.resolve(__dirname, 'src'),
- },
- },
- server: {
- port: 8080,
- proxy: {
- // 127.0.0.1 (not localhost / 0.0.0.0): on macOS, localhost resolves to
- // ::1 first, and AirPlay Receiver also listens on *:5000. uvicorn binds
- // 127.0.0.1 only, so the explicit IPv4 address dodges the collision.
- '^/api': {
- target: 'http://127.0.0.1:5000',
- changeOrigin: true,
- },
- },
- },
-});
diff --git a/app/vite.config.ts b/app/vite.config.ts
new file mode 100644
index 00000000..788ec420
--- /dev/null
+++ b/app/vite.config.ts
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2026 Open Reaction Database Project Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 8080, // parity with old vue app
+ proxy: {
+ '/api': {
+ target: 'http://127.0.0.1:5000',
+ changeOrigin: true,
+ },
+ },
+ },
+});
diff --git a/ord_interface/Dockerfile b/ord_interface/Dockerfile
index 9156a7be..02f06f57 100644
--- a/ord_interface/Dockerfile
+++ b/ord_interface/Dockerfile
@@ -74,13 +74,19 @@ COPY ord_interface/editor/build.sh ord_interface/editor
WORKDIR /app/ord-interface/ord_interface/editor
RUN ./build.sh
WORKDIR /app/ord-interface
-# Build the Vue app.
+# Build the SPA (React + TypeScript + Vite).
COPY app/ app/
WORKDIR /app/ord-interface/app
-RUN cp -r /app/ord-interface/ord_interface/standalone/ /app/ord-interface/app/src/ketcher \
+# NOTE(skearnes): package-lock.json is regenerated inside the image because npm
+# stores rollup's platform-specific optional binaries by host OS, so a lockfile
+# captured on macOS (or any non-Linux dev box) won't include
+# @rollup/rollup-linux-x64-gnu and vite build fails with MODULE_NOT_FOUND.
+# See https://github.com/npm/cli/issues/4828.
+RUN cp -r /app/ord-interface/ord_interface/standalone/ /app/ord-interface/app/public/ketcher \
+ && rm -f package-lock.json \
&& npm install \
&& npm run build \
- && mv dist/ /app/ord-interface/vue
+ && mv dist/ /app/ord-interface/spa
WORKDIR /app/ord-interface
# Copy the rest of the editor files.
COPY ord_interface/editor/ ord_interface/editor/
diff --git a/ord_interface/api/search.py b/ord_interface/api/search.py
index 97395293..db835024 100644
--- a/ord_interface/api/search.py
+++ b/ord_interface/api/search.py
@@ -27,7 +27,7 @@
from uuid import uuid4
import psycopg
-from fastapi import APIRouter, BackgroundTasks, Depends, Query, Response, status
+from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Response, status
from ord_schema.logging import get_logger
from ord_schema.orm.database import get_connection_string
from ord_schema.proto import dataset_pb2
@@ -205,6 +205,20 @@ async def get_datasets() -> list[DatasetInfo]:
return [DatasetInfo(**row) async for row in cursor]
+@router.get("/dataset")
+async def get_dataset(dataset_id: str) -> DatasetInfo:
+ """Returns info about a single dataset."""
+ async with get_cursor() as cursor:
+ await cursor.execute(
+ "SELECT dataset_id, name, description, num_reactions FROM dataset WHERE dataset_id = %s",
+ (dataset_id,),
+ )
+ row = await cursor.fetchone()
+ if row is None:
+ raise HTTPException(status_code=404, detail=f"dataset not found: {dataset_id}")
+ return DatasetInfo(**row)
+
+
@router.get("/molfile")
async def get_molfile(smiles: str) -> str:
"""Returns a molblock for the given SMILES."""
diff --git a/ord_interface/api/search_test.py b/ord_interface/api/search_test.py
index 3a847c79..4a86a0a9 100644
--- a/ord_interface/api/search_test.py
+++ b/ord_interface/api/search_test.py
@@ -79,6 +79,19 @@ def test_get_datasets(test_client):
assert len(dataset_info) == 3
+def test_get_dataset(test_client):
+ dataset_id = "ord_dataset-89b083710e2d441aa0040c361d63359f"
+ response = test_client.get("/api/dataset", params={"dataset_id": dataset_id})
+ response.raise_for_status()
+ dataset_info = response.json()
+ assert dataset_info["dataset_id"] == dataset_id
+
+
+def test_get_dataset_not_found(test_client):
+ response = test_client.get("/api/dataset", params={"dataset_id": "ord_dataset-does-not-exist"})
+ assert response.status_code == 404
+
+
def test_get_molfile(test_client):
response = test_client.get("/api/molfile", params={"smiles": "C(=O)N"})
response.raise_for_status()
diff --git a/ord_interface/nginx.conf b/ord_interface/nginx.conf
index 12fab41e..2d1c1a71 100644
--- a/ord_interface/nginx.conf
+++ b/ord_interface/nginx.conf
@@ -29,7 +29,7 @@ http {
listen 8080;
location / {
- root /app/ord-interface/vue;
+ root /app/ord-interface/spa;
index index.html;
try_files $uri $uri/ /index.html;
}