├── Backend
├── .gitignore
├── vercel.json
├── mainRouter.js
├── Routers
│ └── GPT_Router.js
├── package.json
├── Contollers
│ └── GPT_Controller.js
├── README.md
├── App.js
├── Modules
│ └── GPT_Module.js
└── package-lock.json
├── molecule-visualiser
├── src
│ ├── App.css
│ ├── setupTests.js
│ ├── reportWebVitals.js
│ ├── App.js
│ ├── index.js
│ ├── components
│ │ ├── CheckCycle.js
│ │ ├── GraphADT.js
│ │ ├── Navbar.jsx
│ │ ├── AISection.jsx
│ │ ├── EditMolecule.jsx
│ │ └── Molecule.js
│ ├── index.css
│ └── pages
│ │ ├── Teams.jsx
│ │ └── mainApp.jsx
├── tailwind.config.js
├── .gitignore
├── package.json
├── public
│ └── index.html
└── README.md
├── package.json
├── .vercel
├── project.json
└── README.txt
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTIONS.md
└── README.md
/Backend/.gitignore:
--------------------------------------------------------------------------------
1 | .vercel
2 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "three": "^0.164.1"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.vercel/project.json:
--------------------------------------------------------------------------------
1 | {"projectId":"prj_1joCFSrrHrsWnkDpf8411AIH4dqH","orgId":"team_eUKYgDoVvpdDV1vCRDzA6QHW"}
--------------------------------------------------------------------------------
/Backend/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [{ "src": "App.js", "use": "@vercel/node" }],
4 | "routes": [{ "src": "/(.*)", "dest": "App.js" }]
5 | }
6 |
--------------------------------------------------------------------------------
/Backend/mainRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const GPT_Router = require('./Routers/GPT_Router');
5 |
6 | router.use("/", GPT_Router);
7 |
8 | module.exports = router;
--------------------------------------------------------------------------------
/molecule-visualiser/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/Backend/Routers/GPT_Router.js:
--------------------------------------------------------------------------------
1 | const GPT_Controller = require('../Contollers/GPT_Controller');
2 | const express = require('express');
3 |
4 | const router = express.Router();
5 |
6 | router.get("/getinfo/:compound", (req, res) => {
7 | GPT_Controller(req, res);
8 | })
9 |
10 | module.exports = router;
--------------------------------------------------------------------------------
/molecule-visualiser/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx}",
5 | ],
6 | theme: {
7 | extend: {
8 | fontFamily: {
9 | inter: ['Inter', 'sans-serif'],
10 | },
11 | },
12 | },
13 | plugins: [],
14 | }
--------------------------------------------------------------------------------
/molecule-visualiser/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/Backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "App.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@google/generative-ai": "^0.11.3",
13 | "cors": "^2.8.5",
14 | "dotenv": "^16.4.5",
15 | "express": "^4.19.2",
16 | "nodemon": "^3.1.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/molecule-visualiser/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .vercel
26 |
--------------------------------------------------------------------------------
/Backend/Contollers/GPT_Controller.js:
--------------------------------------------------------------------------------
1 | const GPT_Module = require('../Modules/GPT_Module');
2 |
3 |
4 | async function GPT_Controller(req, res){
5 | const compound_formula = req.params.compound;
6 | const response = await GPT_Module(compound_formula);
7 | if(response.responseCode === 200){
8 | res.status(200).json(response.responseBody);
9 | }
10 | else{
11 | res.status(100).json(response.responseBody);
12 | }
13 | }
14 |
15 | module.exports = GPT_Controller;
--------------------------------------------------------------------------------
/Backend/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## API Documentation
3 |
4 | #### Test Server
5 |
6 | ```http
7 | GET /test
8 | ```
9 | ---------------------------------------------------
10 |
11 | #### Get Information
12 |
13 | ```http
14 | GET /getinfo/{compund_name}
15 | ```
16 |
17 | | Parameter | Type | Description |
18 | | :-------- | :------- | :-------------------------------- |
19 | | `compund` | `string` | **Required**. Molecular Formula of the Compound |
20 |
21 | -----------------------------------------------------
22 |
--------------------------------------------------------------------------------
/Backend/App.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const app = express();
4 | const mainRouter = require('./mainRouter');
5 |
6 | app.use(express.json());
7 | app.use(cors());
8 | require('dotenv').config();
9 |
10 | app.use('/', mainRouter);
11 |
12 | app.get("/test", (req, res) => {
13 | res.status(200).json("Welcome to Molecule Visualizer");
14 | })
15 |
16 | app.listen(process.env.PORT || 3000, function(){
17 | console.log("Server listening on port %d in %s mode", this.address().port, app.settings.env);
18 | });
--------------------------------------------------------------------------------
/.vercel/README.txt:
--------------------------------------------------------------------------------
1 | > Why do I have a folder named ".vercel" in my project?
2 | The ".vercel" folder is created when you link a directory to a Vercel project.
3 |
4 | > What does the "project.json" file contain?
5 | The "project.json" file contains:
6 | - The ID of the Vercel project that you linked ("projectId")
7 | - The ID of the user or team your Vercel project is owned by ("orgId")
8 |
9 | > Should I commit the ".vercel" folder?
10 | No, you should not share the ".vercel" folder with anyone.
11 | Upon creation, it will be automatically added to your ".gitignore" file.
12 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/App.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
2 | import NavBar from "./components/Navbar";
3 | import MoleculeVisualizer from "./pages/mainApp";
4 | import Teams from "./pages/Teams";
5 |
6 | export default function App() {
7 | return (
8 |
9 |
10 |
11 | } />
12 | } />
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/components/CheckCycle.js:
--------------------------------------------------------------------------------
1 | export function checkCycle(molecule) {
2 | let visited = {};
3 | for (let atom of molecule.atomList) visited[atom.atomName] = false;
4 | for (let atom of molecule.atomList) {
5 | if (!visited[atom.atomName]) {
6 | if (DFS(null, atom, visited, molecule)) {
7 | return true;
8 | }
9 | }
10 | }
11 | return false;
12 | }
13 |
14 | function DFS(parentAtom, currentAtom, visited, molecule) {
15 | visited[currentAtom.atomName] = true;
16 | for (let neighbour of molecule.getNeighbours(currentAtom)) {
17 | if (!visited[neighbour.atomName]) {
18 | if (DFS(currentAtom, neighbour, visited, molecule)) {
19 | return true;
20 | }
21 | } else if (neighbour.atomName !== parentAtom?.atomName) {
22 | return true;
23 | }
24 | }
25 | return false;
26 | }
--------------------------------------------------------------------------------
/molecule-visualiser/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import '@fontsource/inter/100.css'; /* Thin */
6 | @import '@fontsource/inter/200.css'; /* Extra Light */
7 | @import '@fontsource/inter/300.css'; /* Light */
8 | @import '@fontsource/inter/400.css'; /* Regular */
9 | @import '@fontsource/inter/500.css'; /* Medium */
10 | @import '@fontsource/inter/600.css'; /* Semi Bold */
11 | @import '@fontsource/inter/700.css'; /* Bold */
12 | @import '@fontsource/inter/800.css'; /* Extra Bold */
13 | @import '@fontsource/inter/900.css'; /* Black */
14 |
15 | @layer utilities {
16 | /* Hide scrollbar for Chrome, Safari and Opera */
17 | .no-scrollbar::-webkit-scrollbar {
18 | display: none;
19 | }
20 | /* Hide scrollbar for IE, Edge and Firefox */
21 | .no-scrollbar {
22 | -ms-overflow-style: none; /* IE and Edge */
23 | scrollbar-width: none; /* Firefox */
24 | }
25 | .markdown-container p {
26 | @apply mb-3 text-yellow-100;
27 | }
28 | .markdown-container h1 {
29 | @apply mb-3 text-blue-100 text-center text-xl;
30 | }
31 |
32 | .markdown-container ul,
33 | .markdown-container ol {
34 | @apply ml-2 mb-1 text-green-100;
35 | }
36 |
37 | .markdown-container li {
38 | @apply mb-1;
39 | }
40 |
41 | .markdown-container strong {
42 | @apply font-bold;
43 | }
44 | }
--------------------------------------------------------------------------------
/molecule-visualiser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "molecule-visualiser",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.11.4",
7 | "@emotion/styled": "^11.11.5",
8 | "@fontsource/inter": "^5.0.18",
9 | "@mui/icons-material": "^5.15.18",
10 | "@mui/material": "^5.15.18",
11 | "@testing-library/jest-dom": "^5.17.0",
12 | "@testing-library/react": "^13.4.0",
13 | "@testing-library/user-event": "^13.5.0",
14 | "3dmol": "^2.1.0",
15 | "axios": "^1.8.1",
16 | "d3": "^7.9.0",
17 | "plotly.js": "^2.32.0",
18 | "react": "^18.3.1",
19 | "react-dom": "^18.3.1",
20 | "react-icons": "^5.2.1",
21 | "react-markdown": "^9.0.1",
22 | "react-plotly.js": "^2.6.0",
23 | "react-router-dom": "^7.2.0",
24 | "react-scripts": "5.0.1",
25 | "three": "^0.164.1",
26 | "web-vitals": "^2.1.4"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject"
33 | },
34 | "eslintConfig": {
35 | "extends": [
36 | "react-app",
37 | "react-app/jest"
38 | ]
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | },
52 | "devDependencies": {
53 | "tailwindcss": "^3.4.3"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/molecule-visualiser/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Molecule Visualizer
9 |
13 |
14 |
18 |
19 |
28 | React App
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/components/GraphADT.js:
--------------------------------------------------------------------------------
1 | export class atomNode {
2 | constructor(atomName, hybridisation, atomSymbol) {
3 | this.atomName = atomName;
4 | this.hybridisation = hybridisation;
5 | this.atomSymbol = atomSymbol;
6 | this.coordinates = [0, 0, 0];
7 | this.connections = [];
8 | }
9 | }
10 |
11 | export class Molecule {
12 | constructor() {
13 | this.adjacencyList = {};
14 | this.atomList = [];
15 | }
16 |
17 | getNeighbours(atom) {
18 | if (atom && this.adjacencyList[atom.atomName]) {
19 | return this.adjacencyList[atom.atomName].map(({ atomName }) =>
20 | this.atomList.find((a) => a.atomName === atomName)
21 | );
22 | }
23 | return [];
24 | }
25 |
26 |
27 | addAtoms(atom) {
28 | if (!this.adjacencyList[atom.atomName]) {
29 | this.adjacencyList[atom.atomName] = [];
30 | this.atomList.push(atom);
31 | } else {
32 | }
33 | }
34 |
35 | addBond(atom1, atom2, isSingleBond = false, isDoubleBond = false, isTripleBond = false) {
36 | if (
37 | this.adjacencyList[atom1.atomName] &&
38 | this.adjacencyList[atom2.atomName]
39 | ) {
40 | let flag1 = 0;
41 | for (let i = 0; i < this.adjacencyList[atom1.atomName].length; i++) {
42 | if (this.adjacencyList[atom1.atomName][i].atomName === atom2.atomName) {
43 | flag1 = 1;
44 | break;
45 | }
46 | }
47 | if (!flag1) {
48 | this.adjacencyList[atom1.atomName].push({
49 | atomName: atom2.atomName,
50 | isSingleBond: isSingleBond && !isDoubleBond && !isTripleBond,
51 | isDoubleBond: !isSingleBond && isDoubleBond && !isTripleBond,
52 | isTripleBond: !isSingleBond && !isDoubleBond && isTripleBond
53 | });
54 | }
55 |
56 | let flag2 = 0;
57 | for (let i = 0; i < this.adjacencyList[atom2.atomName].length; i++) {
58 | if (this.adjacencyList[atom2.atomName][i].atomName === atom1.atomName) {
59 | flag2 = 1;
60 | break;
61 | }
62 | }
63 | if (!flag2) {
64 | this.adjacencyList[atom2.atomName].push({
65 | atomName: atom1.atomName,
66 | isSingleBond: isSingleBond && !isDoubleBond && !isTripleBond,
67 | isDoubleBond: !isSingleBond && isDoubleBond && !isTripleBond,
68 | isTripleBond: !isSingleBond && !isDoubleBond && isTripleBond
69 | });
70 | }
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { SiMoleculer } from "react-icons/si";
4 | import { FiMenu, FiX } from "react-icons/fi";
5 |
6 | export default function NavBar() {
7 | const [isOpen, setIsOpen] = useState(false);
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | MOLECULE VISUALIZER
16 |
17 |
18 |
19 |
35 |
36 |
setIsOpen(!isOpen)}
39 | >
40 | {isOpen ? : }
41 |
42 |
43 |
44 | {isOpen && (
45 |
46 |
setIsOpen(false)}
50 | >
51 | VISUALIZER
52 |
53 |
59 | DOCUMENTATION
60 |
61 |
setIsOpen(false)}
65 | >
66 | TEAM
67 |
68 |
69 | )}
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 | samples-test
133 |
--------------------------------------------------------------------------------
/Backend/Modules/GPT_Module.js:
--------------------------------------------------------------------------------
1 | const { GoogleGenerativeAI } = require("@google/generative-ai");
2 | require("dotenv").config();
3 |
4 | const genAI = new GoogleGenerativeAI(process.env.API_KEY);
5 |
6 | const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); //gemini 1.0 pro is raising some issue
7 |
8 | async function Get_Info(compound_formula) {
9 | const response = {
10 | responseCode: 200,
11 | responseBody: "",
12 | };
13 |
14 | try {
15 | console.log(compound_formula)
16 | const prompt = `"I need detailed information about the chemical compound ${compound_formula} ,If there is more isomers consider one yourself and give out response.
17 | Please provide the following details in a clear and structured markdown format:
18 | Before providing the information:
19 | - **validate** the compound formula.
20 | - If the compound is not found or does not exist, **return the following message**: "The chemical compound '${compound_formula}' could not be identified. Please verify the formula or provide a more specific name."
21 | - If the compound is recognized, provide the details in the following clear and structured markdown format:
22 |
23 | # {Compund formula here} it should be bold when rendered under react-markdown it should be in h1 tag
24 | 1. **Chemical Name** 🧪
25 | - **The Chemical name** :
26 | - **the IUPAC name :**. of compund
27 | 2. **Physical Properties**
28 | - **Melting point** ❄️
29 | - **Boiling point** ♨️
30 | - **Density** ⚖️
31 | - **Solubility** 🌊
32 | - **State at room temperature** 🏠
33 | 3. **Chemical Properties** ⚗️
34 | - **Reactivity** 💥
35 | - **pH**
36 | - **stability**
37 | - **known Chemical reactions**.
38 | 4. **Uses** 🏭
39 | - Common applications and industries where this compound is utilized.
40 | 5. **Safety Information** ⚠️
41 | - **Including toxicity** ☠️
42 | - **handling precautions** 🧤
43 | - **safety measures** 🛡️
44 | 6. **Synthesis** 🛠️
45 | - A brief overview of how this compound is typically synthesized or extracted.
46 | 7. **Regulatory Information** 📜
47 | - Any relevant regulatory guidelines
48 | - restrictions associated with this compound.
49 | Ensure the markdown is well-indented and uses bullet points for lists.
50 | Format all topic headers as bold titles using markdown syntax (**)
51 | Provide an extra line break between sections for better readability"
52 | Just respond with that format and no html tags in output and no extra messages`;
53 | const result = await model.generateContent(prompt);
54 | const GPTresponse = result.response.candidates[0].content.parts[0].text;
55 | response.responseBody = GPTresponse;
56 | console.log(response.responseBody);
57 | } catch (error) {
58 | response.responseCode = 100;
59 | response.responseBody = error;
60 | console.log(response.responseBody);
61 | }
62 | return response;
63 | }
64 |
65 | module.exports = Get_Info;
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ---
10 |
11 | ## Our Standards
12 |
13 | Examples of behavior that contributes to a positive environment for our community include:
14 | - Demonstrating respect and empathy toward all community members.
15 | - Being constructive in feedback and accepting constructive criticism gracefully.
16 | - Focusing on what is best for the community and project.
17 | - Showing kindness and courtesy to others.
18 |
19 | Examples of unacceptable behavior include:
20 | - The use of sexualized language or imagery, and unwelcome sexual attention or advances.
21 | - Trolling, insulting, or derogatory comments, and personal or political attacks.
22 | - Public or private harassment.
23 | - Publishing others’ private information without their explicit permission.
24 | - Other conduct which could reasonably be considered inappropriate in a professional setting.
25 |
26 | ---
27 |
28 | ## Responsibilities
29 |
30 | Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior. They will take appropriate and fair corrective action in response to any behavior they deem inappropriate, threatening, offensive, or harmful.
31 |
32 | ---
33 |
34 | ## Enforcement
35 |
36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at **[your-email@example.com]**. All complaints will be reviewed and investigated promptly and fairly.
37 |
38 | The project team is obligated to respect the privacy and security of the reporter of any incident.
39 |
40 | ---
41 |
42 | ## Enforcement Guidelines
43 |
44 | Project maintainers will follow these guidelines to determine the consequences for any action they deem to be in violation of this Code of Conduct:
45 |
46 | 1. **Correction**
47 | A private, written warning with clarity on the problematic behavior and expectations moving forward.
48 |
49 | 2. **Warning**
50 | A formal warning issued for repeat offenses or major violations.
51 |
52 | 3. **Temporary Ban**
53 | Temporary suspension from the community for continued or severe violations.
54 |
55 | 4. **Permanent Ban**
56 | Permanent removal from the community for egregious violations.
57 |
58 | ---
59 |
60 | ## Attribution
61 |
62 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct/](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
63 |
64 | For answers to common questions, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
65 |
--------------------------------------------------------------------------------
/molecule-visualiser/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/CONTRIBUTIONS.md:
--------------------------------------------------------------------------------
1 | # Contributions Guide
2 |
3 | Thank you for your interest in contributing to the **Molecule Visualiser** project! This guide will help you understand how to contribute effectively.
4 |
5 | ---
6 |
7 | ## Table of Contents
8 | 1. [Getting Started](#getting-started)
9 | 2. [How to Contribute](#how-to-contribute)
10 | 3. [Code of Conduct](#code-of-conduct)
11 | 4. [Reporting Issues](#reporting-issues)
12 | 5. [Creating Pull Requests](#creating-pull-requests)
13 | 6. [Style Guide](#style-guide)
14 |
15 | ---
16 |
17 | ## Getting Started
18 |
19 | To contribute:
20 | 1. Fork the repository from [Molecule Visualiser](https://github.com/TharunKumarrA/Molecule-Visualiser) and clone it to your local machine:
21 | ```bash
22 | git clone https://github.com/YourUsername/Molecule-Visualiser.git
23 | ```
24 | ## Set Up the Project
25 |
26 | - Follow the instructions in the `README.md` to set up the project.
27 | - Ensure your environment matches the project requirements.
28 |
29 | ---
30 |
31 | ## How to Contribute
32 |
33 | ### Issues
34 | - Check the [Issues](https://github.com/TharunKumarrA/Molecule-Visualiser/issues) section for open tickets.
35 | - If you want to work on an existing issue, comment on the issue to let others know you're working on it.
36 | - If you find a bug or have a feature request, [create a new issue](https://github.com/TharunKumarrA/Molecule-Visualiser/issues/new).
37 |
38 | ### Feature Requests
39 | - Before starting work on a new feature:
40 | - Discuss the feature in an existing issue or create a new one.
41 | - Ensure it aligns with the project's goals.
42 |
43 | ---
44 |
45 | ## Code of Conduct
46 |
47 | This project follows the [Contributor Covenant](https://www.contributor-covenant.org/).
48 | All contributors must adhere to it. Be respectful and constructive in all interactions.
49 |
50 | ---
51 |
52 | ## Reporting Issues
53 |
54 | When reporting an issue:
55 | - Provide a clear and descriptive title.
56 | - Include steps to reproduce the issue.
57 | - Share relevant logs or screenshots if applicable.
58 | - Specify the environment (e.g., operating system, browser, Node.js version).
59 | ## Creating Pull Requests
60 |
61 | Follow these steps to create a pull request:
62 |
63 | 1. **Create a new branch** from `main` or the relevant base branch:
64 | ```bash
65 | git checkout -b feature/your-feature-name
66 | ```
67 | ## Make Your Changes
68 |
69 | - Ensure code quality and thorough testing.
70 |
71 | ### Commit Your Changes
72 | ```bash
73 | git commit -m "Add a concise and descriptive commit message"
74 | ```
75 | ### Push the Branch to Your Fork
76 | ```bash
77 | git push origin feature/your-feature-name
78 | ```
79 | ### Open a Pull Request (PR)
80 |
81 | - Open a pull request (PR) to the `main` branch of the original repository.
82 |
83 | ---
84 |
85 | ## PR Guidelines
86 |
87 | - Provide a detailed description of the changes in the PR.
88 | - Reference related issues (e.g., `Closes #123`).
89 | - Ensure the PR passes all checks (tests, linters).
90 |
91 | ---
92 |
93 | ## Style Guide
94 |
95 | ### Code
96 | - Follow the style conventions mentioned in the project documentation (e.g., ESLint rules for JavaScript).
97 | - Write clear, concise, and well-documented code.
98 |
99 | ### Commits
100 | - Use descriptive commit messages (e.g., `Fix bug in molecule visualization logic`).
101 | - Group related changes into a single commit.
102 |
103 | ---
104 |
105 | ## Acknowledgments
106 |
107 | Thank you for taking the time to contribute! Your efforts make this project better for everyone.
108 |
109 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/pages/Teams.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import NavBar from "../components/Navbar";
4 |
5 | const teamMembers = [
6 | "TharunKumarrA",
7 | "Naganathan05",
8 | "Lowkik-Sai",
9 | "Hariprasath8064",
10 | "S-Bharath16",
11 | "adithya-menon-r",
12 | "Twinn-github09",
13 | ];
14 |
15 | export default function Teams() {
16 | const [profiles, setProfiles] = useState([]);
17 | const [loading, setLoading] = useState(true);
18 |
19 | const GITHUB_TOKEN = process.env.REACT_APP_GITHUB_TOKEN;
20 |
21 | useEffect(() => {
22 | async function fetchProfiles() {
23 | setLoading(true);
24 | const promises = teamMembers.map(async (username) => {
25 | try {
26 | const res = await axios.get(
27 | `https://api.github.com/users/${username}`,
28 | {
29 | headers: {
30 | Authorization: `Bearer ${GITHUB_TOKEN}`,
31 | },
32 | }
33 | );
34 | return {
35 | name: res.data.name || res.data.login,
36 | username: res.data.login,
37 | avatar: res.data.avatar_url,
38 | profileUrl: res.data.html_url,
39 | };
40 | } catch (error) {
41 | console.error("Error fetching data for", username);
42 | return null;
43 | }
44 | });
45 |
46 | const results = await Promise.all(promises);
47 | setProfiles(results.filter((profile) => profile !== null));
48 | setLoading(false);
49 | }
50 |
51 | fetchProfiles();
52 | }, []);
53 |
54 | return (
55 |
56 |
57 |
58 |
Meet Our Team
59 | {loading ? (
60 |
61 | Fetching team data...
62 |
63 | ) : (
64 |
104 | )}
105 |
106 |
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Molecule Visualizer
3 |
4 |
5 | Molecule Visualizer is an innovative data structures and algorithms initiative designed to represent and manipulate chemical molecules in a three-dimensional space using graph data structures.
6 | Each atom is represented as a node, and each bond as an edge, allowing for a flexible and dynamic representation of molecular structures.
7 | The construction of the molecular graph employs Breadth-First Search (BFS) to ensure comprehensive and systematic node traversal, facilitating the accurate assembly of the molecule. Depth-First Search (DFS) is implemented for cycle detection, crucial for identifying rings and complex substructures within the molecule.
8 |
9 | ## Key Features
10 |
11 | * Detailed information sharing about the constructed molecules **Physical** and **Chemical** Properties.
12 | * Bond angle calculations and molecular geometry analysis.
13 | * Algorithmic efficiency for real-time updates and smooth interactions.
14 |
15 |
16 |
17 | ## Tech Stack
18 |
19 | **Client:** React JS, Material UI, Tailwind CSS
20 |
21 | **Server:** Node JS, Express JS
22 |
23 | **Plotting:** Plotly JS
24 |
25 |
26 | ## Contributing
27 |
28 | Contributions are always welcome!
29 |
30 | See `contributing.md` for ways to get started.
31 |
32 | Please adhere to this project's `code of conduct`.
33 |
34 | # Local Development:
35 |
36 | ## Steps to get the Application Running at Local:
37 |
38 | ### 1. Clone the Repository
39 |
40 | ```bash
41 | git clone https://github.com/TharunKumarrA/Molecule-Visualiser.git
42 | cd Molecule-Visualiser
43 | ```
44 |
45 | ### 2. Install Backend Dependencies
46 |
47 | ```bash
48 | cd Backend
49 | npm install
50 | cd ..
51 | ```
52 |
53 | ### 3. Install Frontend Dependencies
54 |
55 | ```bash
56 | cd molecule-visualiser
57 | npm install
58 | cd ..
59 | ```
60 |
61 | ### 4. Configure Environment Variables
62 |
63 | > [!Important]
64 | > Just for the sake of security, the `.env` file is not included in the repository.
65 | Create a `.env` file in the root directory of the project and add the following key-value pairs. Replace placeholders with actual values. Ensure there are no spaces or quotes, and values are entered as plain text.
66 |
67 | ```env
68 | # Server Port Number
69 | PORT=
70 |
71 | # API Key for Gemini 1.5 AI Model.
72 | API_KEY=
73 | ```
74 |
75 |
76 | ### 5. Start the Backend Server
77 |
78 | ```bash
79 | cd Backend
80 | nodemon App.js
81 | ```
82 |
83 | ### 6. Start the Frontend Server
84 |
85 | ```bash
86 | cd molecule-visualiser
87 | npm run start
88 | ```
89 | ## API Utilized
90 |
91 | [Gemini 1.5 Flash](https://ai.google.dev/gemini-api/docs/models/gemini#gemini-1.5-flash)
92 |
93 |
94 | ## API Documentation
95 |
96 | [API-Reference](/Backend/README.md)
97 |
98 |
99 | ## Developers
100 |
101 | - `Tharun Kumarr A`
102 | - `Naganathan M R`
103 | - `Lowkik Sai P`
104 | - `Praveen K`
105 | - `Bharath S`
106 | - `Hariprasath M`
107 |
108 | ## Future Ideas for **Molecule Visualizer**:
109 |
110 | * **Validation** of the compounds that has be entered by the user.
111 | * **Extending** the available hybridisation of molecules to **sp3d** and **sp3d2**.
112 | * **Allowing** for various type of elements available in the periodic table.
113 |
114 | ## FAQ
115 |
116 | #### How to build the Molecule ?
117 |
118 | * **Add Atoms:** Navigate to the menu bar at the right side of the page. Initially you need to add all the atoms with respective **hybridisations** that are present in your molecule using the **Add Atoms** menu.
119 |
120 | * **Add Bond:** Menu in the right side will be comprising a option named **Add Bond**. Using this option select respective two atoms and the type of bond `Single, Double, Triple`. Then add bond will connect those two atoms in the 3d space.
121 |
122 | #### What are these Examples ?
123 |
124 | * These are prebuilt molecule templates for visualizing. These include the most common **Organic** and **Inorganic** compounds for fast visualization.
125 |
126 |
127 | #### How to find if a Molecule is Cyclic/Acyclic ?
128 |
129 | * Once you constructed the molecule using the option available, you can see the **Check Cycle** information in the bottom of the menu bar present at the right. This indicates whether the constructed molecule is **Cyclic** or **Acyclic**.
130 |
131 | #### How get properties of constructed moelcule ?
132 |
133 | * Once you have constrcuted the molecule, use the option **Get Data** available at the top left. This option when trigerred whill fetch you all the **Chemical** and **Physical** Properties of the molecule that has been constrcuted.
134 |
135 | ```mermaid
136 | graph TD
137 | A[Start] --> B[Add Atoms]
138 | B --> C[Add Bond]
139 | C --> D{Cycle Detection}
140 | D -->|Cyclic| E[Identify Rings]
141 | D -->|Acyclic| F[Proceed]
142 | E --> F
143 | F --> G[Calculate Bond Angles]
144 | G --> H[Analyze Molecular Geometry]
145 | H --> I[Get Physical/Chemical Properties]
146 | I --> J[Display Results]
147 | J --> K[Real-time Updates]
148 | K --> L[End]
149 |
150 | ```
151 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/components/AISection.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import axios from "axios";
3 | import ReactMarkdown from "react-markdown";
4 |
5 | export default function AISection({ compoundFormula, resetAI }) {
6 | const [res, setResponse] = useState(null);
7 | const [isLoading, setIsLoading] = useState(false);
8 | const [hasDisplayedInfo, setHasDisplayedInfo] = useState(false);
9 |
10 | const serverPort = process.env.PORT || 5000;
11 | const BASE_URL =
12 | process.env.REACT_APP_BACKEND_URL || "https://molecule-backend.vercel.app";
13 | console.log(BASE_URL);
14 |
15 | // Reset the response when resetAI changes to true
16 | useEffect(() => {
17 | if (resetAI) {
18 | setResponse(null);
19 | setHasDisplayedInfo(false);
20 | }
21 | }, [resetAI]);
22 |
23 | // Reset the response when compoundFormula becomes empty
24 | useEffect(() => {
25 | if (!compoundFormula || compoundFormula === "") {
26 | setResponse(null);
27 | setHasDisplayedInfo(false);
28 | } else if (res === null) {
29 | // If we have a compound formula but no response, we should show the button
30 | setHasDisplayedInfo(false);
31 | }
32 | }, [compoundFormula, res]);
33 |
34 | const handleGetData = () => {
35 | // Don't fetch if there's no compound formula
36 | if (!compoundFormula || compoundFormula === "") {
37 | return;
38 | }
39 |
40 | setIsLoading(true);
41 | axios
42 | .get(`${BASE_URL}/getinfo/${compoundFormula}`)
43 | .then((response) => {
44 | console.log(response.data);
45 | setResponse(response.data);
46 | setHasDisplayedInfo(true);
47 | setIsLoading(false);
48 | })
49 | .catch((error) => {
50 | console.error("Error fetching data:", error);
51 | setIsLoading(false);
52 | });
53 | };
54 |
55 | return (
56 |
57 | {isLoading ? (
58 |
59 |
64 |
72 |
76 |
80 |
81 | Processing...
82 |
83 |
84 | ) : (
85 | <>
86 | {!res && compoundFormula && !hasDisplayedInfo && (
87 |
91 | Get Molecule Info
92 |
93 | )}
94 | {!compoundFormula && (
95 |
96 | Build a molecule to get information
97 |
98 | )}
99 | {res && (
100 |
101 |
102 |
103 | {res}
104 |
105 |
106 |
107 | )}
108 | >
109 | )}
110 |
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/pages/mainApp.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Plot from "react-plotly.js";
3 | import {
4 | createAtomNode,
5 | addAtoms,
6 | addBonds,
7 | buildMolecule,
8 | getCoordinates,
9 | } from "../components/Molecule";
10 | import { Molecule } from "../components/GraphADT";
11 | import NavBar from "../components/Navbar";
12 | import EditMolecule from "../components/EditMolecule";
13 | import AISection from "../components/AISection";
14 | import { checkCycle } from "../components/CheckCycle";
15 |
16 | const MoleculeVisualizer = () => {
17 | const [atomsList, setAtomsList] = useState([]);
18 | const [molecule, setMolecule] = useState(new Molecule());
19 | const [trigger, setTrigger] = useState(0);
20 | const [resetAI, setResetAI] = useState(false);
21 |
22 | const [atomCounters, setAtomCounters] = useState({
23 | C: 0,
24 | H: 0,
25 | O: 0,
26 | N: 0,
27 | P: 0,
28 | S: 0,
29 | F: 0,
30 | Cl: 0,
31 | });
32 |
33 | const [compoundFormula, setCompoundFormula] = useState("");
34 |
35 | const generateCompoundFormula = () => {
36 | let formula = "";
37 | for (const [atom, count] of Object.entries(atomCounters)) {
38 | if (count > 0) {
39 | formula += atom;
40 | if (count > 1) {
41 | formula += count;
42 | }
43 | }
44 | }
45 | return formula;
46 | };
47 |
48 | useEffect(() => {
49 | const newCompoundFormula = generateCompoundFormula();
50 | setCompoundFormula(newCompoundFormula);
51 | console.log("Compound Formula: ", newCompoundFormula);
52 | }, [atomCounters]);
53 |
54 | useEffect(() => {
55 | getCoordinates(molecule);
56 | console.log("In useEffect");
57 | }, [trigger, molecule]);
58 |
59 | const incrementAtomCounter = (atomType) => {
60 | setAtomCounters((prevCounters) => ({
61 | ...prevCounters,
62 | [atomType]: prevCounters[atomType] + 1,
63 | }));
64 | };
65 |
66 | const handleDataFromEditMolecule = (data) => {
67 | if (data.type === "atom") {
68 | const atomCounter = atomCounters[data.atomSymbol];
69 | const atomName = data.atomSymbol + atomCounter;
70 | const atom = createAtomNode(
71 | atomName,
72 | data.hybridisation,
73 | data.atomSymbol
74 | );
75 | addAtoms(molecule, atom);
76 | console.log("Atom added: ", atom.atomName);
77 | console.log(data);
78 | incrementAtomCounter(data.atomSymbol);
79 | } else if (data.type === "bond") {
80 | addBonds(
81 | molecule,
82 | data.atom1Name,
83 | data.atom2Name,
84 | data.isSingleBond,
85 | data.isDoubleBond,
86 | data.isTripleBond
87 | );
88 | console.log("Bond added between: ", data.atom1Name, data.atom2Name);
89 | console.log(data);
90 | } else if (data.type === "clear") {
91 | // Reset all states when clearing the molecule
92 | setMolecule(new Molecule());
93 | setAtomsList([]);
94 | setAtomCounters({
95 | C: 0,
96 | H: 0,
97 | O: 0,
98 | N: 0,
99 | P: 0,
100 | S: 0,
101 | F: 0,
102 | Cl: 0,
103 | });
104 | setCompoundFormula("");
105 | setTrigger(0); // Reset trigger state
106 | setResetAI(true);
107 | setTimeout(() => setResetAI(false), 100);
108 | }
109 |
110 | setTrigger((prev) => prev + 1);
111 | };
112 |
113 | const handleMoleculeUpdate = (updatedMolecule) => {
114 | // When molecule is updated (especially cleared), set resetAI to true
115 | if (updatedMolecule.atomList.length === 0) {
116 | setResetAI(true);
117 | setTimeout(() => setResetAI(false), 100);
118 | }
119 |
120 | setMolecule(updatedMolecule);
121 | setTrigger(!trigger);
122 | };
123 |
124 | const traceAtoms = {
125 | type: "scatter3d",
126 | mode: "markers+text",
127 | text: molecule.atomList.map((atom) => atom.atomSymbol),
128 | x: molecule.atomList.map((atom) => atom.coordinates[0]),
129 | y: molecule.atomList.map((atom) => atom.coordinates[1]),
130 | z: molecule.atomList.map((atom) => atom.coordinates[2]),
131 | marker: {
132 | size: 12,
133 | opacity: 0.8,
134 | color: molecule.atomList.map((atom) => {
135 | switch (atom.atomSymbol) {
136 | case "C":
137 | return "black";
138 | case "H":
139 | return "#87CEEB"; // Medium Light Blue
140 | case "P":
141 | return "#FFA500"; // Orange
142 | case "Cl":
143 | return "#20B2AA"; // Light Sea Green
144 | case "S":
145 | return "yellow";
146 | case "F":
147 | return "#32CD32";
148 | default:
149 | return "#CD5C5C"; // Indian Red
150 | }
151 | }),
152 | },
153 | };
154 |
155 | const traceSingleBonds = {
156 | type: "scatter3d",
157 | mode: "lines",
158 | line: {
159 | color: "gray",
160 | width: 2,
161 | },
162 | x: [],
163 | y: [],
164 | z: [],
165 | };
166 |
167 | const traceDoubleBonds = {
168 | type: "scatter3d",
169 | mode: "lines",
170 | line: {
171 | color: "red",
172 | width: 4,
173 | },
174 | x: [],
175 | y: [],
176 | z: [],
177 | };
178 |
179 | const traceTripleBonds = {
180 | type: "scatter3d",
181 | mode: "lines",
182 | line: {
183 | color: "black",
184 | width: 4,
185 | },
186 | x: [],
187 | y: [],
188 | z: [],
189 | };
190 |
191 | // Add connections data to traceSingleBonds, traceDoubleBonds, and traceTripleBonds
192 | molecule.atomList.forEach((atom) => {
193 | const atomConnections = molecule.adjacencyList[atom.atomName];
194 | if (atomConnections) {
195 | atomConnections.forEach((connection) => {
196 | const connectedAtom = molecule.atomList.find(
197 | (a) => a.atomName === connection.atomName
198 | );
199 |
200 | if (connectedAtom) {
201 | if (connection.isDoubleBond) {
202 | // Main line
203 | traceDoubleBonds.x.push(
204 | atom.coordinates[0],
205 | connectedAtom.coordinates[0],
206 | null
207 | );
208 | traceDoubleBonds.y.push(
209 | atom.coordinates[1],
210 | connectedAtom.coordinates[1],
211 | null
212 | );
213 | traceDoubleBonds.z.push(
214 | atom.coordinates[2],
215 | connectedAtom.coordinates[2],
216 | null
217 | );
218 |
219 | // Offset line
220 | traceDoubleBonds.x.push(
221 | atom.coordinates[0] + 0.01,
222 | connectedAtom.coordinates[0] + 0.01,
223 | null
224 | );
225 | traceDoubleBonds.y.push(
226 | atom.coordinates[1],
227 | connectedAtom.coordinates[1],
228 | null
229 | );
230 | traceDoubleBonds.z.push(
231 | atom.coordinates[2] + 0.01,
232 | connectedAtom.coordinates[2],
233 | null
234 | );
235 | } else if (connection.isSingleBond && !connection.isTripleBond) {
236 | traceSingleBonds.x.push(
237 | atom.coordinates[0],
238 | connectedAtom.coordinates[0],
239 | null
240 | );
241 | traceSingleBonds.y.push(
242 | atom.coordinates[1],
243 | connectedAtom.coordinates[1],
244 | null
245 | );
246 | traceSingleBonds.z.push(
247 | atom.coordinates[2],
248 | connectedAtom.coordinates[2],
249 | null
250 | );
251 | } else if (connection.isTripleBond) {
252 | // Main line
253 | traceTripleBonds.x.push(
254 | atom.coordinates[0],
255 | connectedAtom.coordinates[0],
256 | null
257 | );
258 | traceTripleBonds.y.push(
259 | atom.coordinates[1],
260 | connectedAtom.coordinates[1],
261 | null
262 | );
263 | traceTripleBonds.z.push(
264 | atom.coordinates[2],
265 | connectedAtom.coordinates[2],
266 | null
267 | );
268 |
269 | // Offset lines
270 | traceTripleBonds.x.push(
271 | atom.coordinates[0] + 0.01,
272 | connectedAtom.coordinates[0] + 0.01,
273 | null
274 | );
275 | traceTripleBonds.y.push(
276 | atom.coordinates[1] + 0.01,
277 | connectedAtom.coordinates[1] + 0.01,
278 | null
279 | );
280 | traceTripleBonds.z.push(
281 | atom.coordinates[2] + 0.01,
282 | connectedAtom.coordinates[2] + 0.01,
283 | null
284 | );
285 |
286 | traceTripleBonds.x.push(
287 | atom.coordinates[0] - 0.01,
288 | connectedAtom.coordinates[0] - 0.01,
289 | null
290 | );
291 | traceTripleBonds.y.push(
292 | atom.coordinates[1] - 0.01,
293 | connectedAtom.coordinates[1] - 0.01,
294 | null
295 | );
296 | traceTripleBonds.z.push(
297 | atom.coordinates[2] - 0.01,
298 | connectedAtom.coordinates[2] - 0.01,
299 | null
300 | );
301 | }
302 | }
303 | });
304 | }
305 | });
306 |
307 | const layout = {
308 | margin: { l: 0, r: 0, b: 0, t: 0 },
309 | paper_bgcolor: "#1e1e1e", // Dark background for the plot
310 | plot_bgcolor: "#1e1e1e", // Dark background for the plot
311 | font: {
312 | color: "#ffffff", // White font color for better contrast
313 | },
314 | scene: {
315 | xaxis: {
316 | backgroundcolor: "#1e1e1e",
317 | gridcolor: "#444444",
318 | showbackground: true,
319 | zerolinecolor: "#444444",
320 | },
321 | yaxis: {
322 | backgroundcolor: "#1e1e1e",
323 | gridcolor: "#444444",
324 | showbackground: true,
325 | zerolinecolor: "#444444",
326 | },
327 | zaxis: {
328 | backgroundcolor: "#1e1e1e",
329 | gridcolor: "#444444",
330 | showbackground: true,
331 | zerolinecolor: "#444444",
332 | },
333 | annotations:
334 | molecule.atomList.length === 0
335 | ? [
336 | {
337 | text: "Add atoms and bonds using the menu on the right.",
338 | showarrow: false,
339 | x: 0,
340 | y: 0,
341 | z: 0,
342 | font: { size: 20, color: "#ffffff" },
343 | },
344 | ]
345 | : [],
346 | },
347 | };
348 |
349 | const atomMenuItems = [
350 | { value: "C", label: "C" },
351 | { value: "H", label: "H" },
352 | { value: "O", label: "O" },
353 | { value: "N", label: "N" },
354 | { value: "P", label: "P" },
355 | { value: "S", label: "S" },
356 | { value: "F", label: "F" },
357 | { value: "Cl", label: "Cl" },
358 | ];
359 |
360 | const getBondMenuItems = () => {
361 | const bondMenuItems = [];
362 |
363 | molecule.atomList.forEach((atom) => {
364 | bondMenuItems.push({ value: atom.atomName, label: atom.atomName });
365 | });
366 |
367 | // Add placeholder items if there aren't enough atoms
368 | if (bondMenuItems.length < 2) {
369 | for (let i = bondMenuItems.length; i < 2; i++) {
370 | bondMenuItems.push({ value: `A${i + 1}`, label: `A${i + 1}` });
371 | }
372 | }
373 |
374 | return bondMenuItems;
375 | };
376 |
377 | const sampleMolecules = ["CH4", "C2H6", "C6H6", "H2O", "SF6", "PCl5"];
378 |
379 | return (
380 |
381 |
382 |
383 | {/* Warning message for small and medium screens */}
384 |
385 |
386 |
396 |
397 |
398 |
399 |
Unsupported Screen Size
400 |
401 | Please use a larger screen for the best experience.
402 |
403 |
404 |
405 |
406 | {/* Main content, hidden on sm and md screens */}
407 |
408 |
411 |
421 |
422 |
435 |
436 |
437 |
438 |
439 | );
440 | };
441 |
442 | export default MoleculeVisualizer;
443 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/components/EditMolecule.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Accordion from "@mui/material/Accordion";
3 | import AccordionDetails from "@mui/material/AccordionDetails";
4 | import AccordionSummary from "@mui/material/AccordionSummary";
5 | import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
6 | import Radio from "@mui/material/Radio";
7 | import RadioGroup from "@mui/material/RadioGroup";
8 | import FormControlLabel from "@mui/material/FormControlLabel";
9 | import MenuItem from "@mui/material/MenuItem";
10 | import Select from "@mui/material/Select";
11 | import Button from "@mui/material/Button";
12 | import { checkCycle } from "./CheckCycle";
13 | import { Molecule } from "./GraphADT";
14 | import { addAtoms, addBonds, createAtomNode, getCoordinates } from "./Molecule";
15 |
16 | export default function EditMolecule({
17 | atomMenuItems,
18 | bondMenuItems,
19 | sampleMolecules,
20 | molecule,
21 | setMolecule,
22 | atomsList,
23 | setAtomsList,
24 | handleDataFromEditMolecule,
25 | atomCounters,
26 | handleMoleculeUpdate,
27 | setAtomCounters,
28 | }) {
29 | const [expandedAccordion, setExpandedAccordion] = useState("panel1");
30 | const [atomData, setAtomData] = useState({
31 | atomType: "C",
32 | hybridization: "sp",
33 | });
34 | const [bondData, setBondData] = useState({
35 | bondFrom: bondMenuItems[0].value,
36 | bondTo: bondMenuItems[1].value,
37 | bondType: "single",
38 | });
39 | const [selectedMolecule, setSelectedMolecule] = useState(sampleMolecules[0]);
40 |
41 | const handleChangeAccordion = (panel) => (event, newExpanded) => {
42 | setExpandedAccordion(newExpanded ? panel : null);
43 | };
44 |
45 | const handleAtomChange = (e) => {
46 | const { name, value } = e.target;
47 | setAtomData((prevData) => ({ ...prevData, [name]: value }));
48 | console.log("currently selected atom type: ", value);
49 | };
50 |
51 | const handleBondChange = (e) => {
52 | const { name, value } = e.target;
53 | setBondData((prevData) => ({ ...prevData, [name]: value }));
54 | console.log("currently selected bond type: ", value);
55 | };
56 |
57 | const handleClearMolecule = () => {
58 | // Create a new empty molecule
59 | const emptyMolecule = new Molecule();
60 |
61 | // Reset molecule and related states
62 | setMolecule(emptyMolecule);
63 | setAtomsList([]);
64 | setAtomCounters({ C: 0, H: 0, O: 0, N: 0, P: 0, S: 0, F: 0, Cl: 0 });
65 |
66 | // Reset form states to defaults
67 | setAtomData({
68 | atomType: "C",
69 | hybridization: "sp",
70 | });
71 |
72 | setBondData({
73 | bondFrom: bondMenuItems[0]?.value || "",
74 | bondTo: bondMenuItems[1]?.value || "",
75 | bondType: "single",
76 | });
77 |
78 | // Reset accordion expansion
79 | setExpandedAccordion("panel1");
80 |
81 | // Reset selected molecule dropdown
82 | setSelectedMolecule(sampleMolecules[0]);
83 |
84 | // If there's a molecule update handler, call it
85 | if (handleMoleculeUpdate) {
86 | handleMoleculeUpdate(emptyMolecule);
87 | }
88 |
89 | // Trigger AI reset
90 | handleDataFromEditMolecule({ type: "clear" });
91 |
92 | console.log("Molecule and all states cleared");
93 | };
94 |
95 | const handleShowMolecule = () => {
96 | switch (selectedMolecule) {
97 | case "CH4":
98 | const methane = new Molecule();
99 |
100 | addAtoms(methane, createAtomNode("C0", "sp3", "C"));
101 | addAtoms(methane, createAtomNode("H0", "sp", "H"));
102 | addAtoms(methane, createAtomNode("H1", "sp", "H"));
103 | addAtoms(methane, createAtomNode("H2", "sp", "H"));
104 | addAtoms(methane, createAtomNode("H3", "sp", "H"));
105 |
106 | addBonds(methane, "C0", "H0", true, false, false);
107 | addBonds(methane, "C0", "H1", true, false, false);
108 | addBonds(methane, "C0", "H2", true, false, false);
109 | addBonds(methane, "C0", "H3", true, false, false);
110 |
111 | setAtomCounters({ C: 1, H: 4, O: 0, N: 0 });
112 | getCoordinates(methane);
113 | setMolecule(methane);
114 | break;
115 | case "PCl5":
116 | const phosphorusPentachloride = new Molecule();
117 |
118 | addAtoms(phosphorusPentachloride, createAtomNode("P0", "sp3d", "P"));
119 | addAtoms(phosphorusPentachloride, createAtomNode("Cl0", "sp", "Cl"));
120 | addAtoms(phosphorusPentachloride, createAtomNode("Cl1", "sp", "Cl"));
121 | addAtoms(phosphorusPentachloride, createAtomNode("Cl2", "sp", "Cl"));
122 | addAtoms(phosphorusPentachloride, createAtomNode("Cl3", "sp", "Cl"));
123 | addAtoms(phosphorusPentachloride, createAtomNode("Cl4", "sp", "Cl"));
124 |
125 | addBonds(phosphorusPentachloride, "P0", "Cl0", true, false, false);
126 | addBonds(phosphorusPentachloride, "P0", "Cl1", true, false, false);
127 | addBonds(phosphorusPentachloride, "P0", "Cl2", true, false, false);
128 | addBonds(phosphorusPentachloride, "P0", "Cl3", true, false, false);
129 | addBonds(phosphorusPentachloride, "P0", "Cl4", true, false, false);
130 |
131 | setAtomCounters({ C: 0, H: 0, O: 0, N: 0, P: 1, Cl: 5 });
132 | getCoordinates(phosphorusPentachloride);
133 | setMolecule(phosphorusPentachloride);
134 | break;
135 | case "C2H6":
136 | const ethane = new Molecule();
137 |
138 | addAtoms(ethane, createAtomNode("C0", "sp3", "C"));
139 | addAtoms(ethane, createAtomNode("C1", "sp3", "C"));
140 | addAtoms(ethane, createAtomNode("H0", "sp", "H"));
141 | addAtoms(ethane, createAtomNode("H1", "sp", "H"));
142 | addAtoms(ethane, createAtomNode("H2", "sp", "H"));
143 | addAtoms(ethane, createAtomNode("H3", "sp", "H"));
144 | addAtoms(ethane, createAtomNode("H4", "sp", "H"));
145 | addAtoms(ethane, createAtomNode("H5", "sp", "H"));
146 |
147 | addBonds(ethane, "C0", "H0", true, false, false);
148 | addBonds(ethane, "C0", "H1", true, false, false);
149 | addBonds(ethane, "C0", "H2", true, false, false);
150 | addBonds(ethane, "C0", "C1", true, false, false);
151 | addBonds(ethane, "C1", "H3", true, false, false);
152 | addBonds(ethane, "C1", "H4", true, false, false);
153 | addBonds(ethane, "C1", "H5", true, false, false);
154 |
155 | setAtomCounters({ C: 2, H: 6, O: 0, N: 0 });
156 | getCoordinates(ethane);
157 | setMolecule(ethane);
158 | break;
159 | case "C6H6":
160 | const benzene = new Molecule();
161 | addAtoms(benzene, createAtomNode("C0", "sp2", "C"));
162 | addAtoms(benzene, createAtomNode("C1", "sp2", "C"));
163 | addAtoms(benzene, createAtomNode("C2", "sp2", "C"));
164 | addAtoms(benzene, createAtomNode("C3", "sp2", "C"));
165 | addAtoms(benzene, createAtomNode("C4", "sp2", "C"));
166 | addAtoms(benzene, createAtomNode("C5", "sp2", "C"));
167 | addAtoms(benzene, createAtomNode("H0", "sp", "H"));
168 | addAtoms(benzene, createAtomNode("H1", "sp", "H"));
169 | addAtoms(benzene, createAtomNode("H2", "sp", "H"));
170 | addAtoms(benzene, createAtomNode("H3", "sp", "H"));
171 | addAtoms(benzene, createAtomNode("H4", "sp", "H"));
172 | addAtoms(benzene, createAtomNode("H5", "sp", "H"));
173 |
174 | addBonds(benzene, "C0", "H0", true, false, false);
175 | addBonds(benzene, "C0", "C1", true, false, false);
176 | addBonds(benzene, "C1", "H1", true, false, false);
177 | addBonds(benzene, "C1", "C2", false, true, false);
178 | addBonds(benzene, "C2", "H2", true, false, false);
179 | addBonds(benzene, "C2", "C3", true, false, false);
180 | addBonds(benzene, "C3", "H3", true, false, false);
181 | addBonds(benzene, "C3", "C4", false, true, false);
182 | addBonds(benzene, "C4", "H4", true, false, false);
183 | addBonds(benzene, "C4", "C5", true, false, false);
184 | addBonds(benzene, "C5", "H5", true, false, false);
185 | addBonds(benzene, "C5", "C0", false, true, false);
186 |
187 | setAtomCounters({ C: 6, H: 6, O: 0, N: 0 });
188 | getCoordinates(benzene);
189 | setMolecule(benzene);
190 | break;
191 | case "H2O":
192 | const water = new Molecule();
193 |
194 | addAtoms(water, createAtomNode("O0", "sp3", "O"));
195 | addAtoms(water, createAtomNode("H0", "sp", "H"));
196 | addAtoms(water, createAtomNode("H1", "sp", "H"));
197 |
198 | addBonds(water, "O0", "H0", true, false, false);
199 | addBonds(water, "O0", "H1", true, false, false);
200 |
201 | setAtomCounters({ C: 0, H: 2, O: 1, N: 0 });
202 | getCoordinates(water);
203 | setMolecule(water);
204 | break;
205 | case "SF6":
206 | const sulfurHexaFluoride = new Molecule();
207 |
208 | addAtoms(sulfurHexaFluoride, createAtomNode("S0", "sp3d2", "S"));
209 | addAtoms(sulfurHexaFluoride, createAtomNode("F0", "sp", "F"));
210 | addAtoms(sulfurHexaFluoride, createAtomNode("F1", "sp", "F"));
211 | addAtoms(sulfurHexaFluoride, createAtomNode("F2", "sp", "F"));
212 | addAtoms(sulfurHexaFluoride, createAtomNode("F3", "sp", "F"));
213 | addAtoms(sulfurHexaFluoride, createAtomNode("F4", "sp", "F"));
214 | addAtoms(sulfurHexaFluoride, createAtomNode("F5", "sp", "F"));
215 |
216 | addBonds(sulfurHexaFluoride, "S0", "F0", true, false, false);
217 | addBonds(sulfurHexaFluoride, "S0", "F1", true, false, false);
218 | addBonds(sulfurHexaFluoride, "S0", "F2", true, false, false);
219 | addBonds(sulfurHexaFluoride, "S0", "F3", true, false, false);
220 | addBonds(sulfurHexaFluoride, "S0", "F4", true, false, false);
221 | addBonds(sulfurHexaFluoride, "S0", "F5", true, false, false);
222 |
223 | setAtomCounters({ C: 0, H: 0, O: 0, N: 0, S: 1, F: 6 });
224 | getCoordinates(sulfurHexaFluoride);
225 | setMolecule(sulfurHexaFluoride);
226 | break;
227 | default:
228 | break;
229 | }
230 | };
231 |
232 | const handleSubmitAtom = (e) => {
233 | e.preventDefault();
234 | const data = {
235 | type: "atom",
236 | atomName: atomData.atomType + atomsList.length,
237 | hybridisation: atomData.hybridization,
238 | atomSymbol: atomData.atomType,
239 | };
240 | handleDataFromEditMolecule(data);
241 | };
242 |
243 | const handleSubmitBond = (e) => {
244 | e.preventDefault();
245 | let single,
246 | double,
247 | triple = false;
248 | if (bondData.bondType === "single") {
249 | single = true;
250 | double = false;
251 | triple = false;
252 | }
253 | if (bondData.bondType === "double") {
254 | single = false;
255 | double = true;
256 | triple = false;
257 | }
258 | if (bondData.bondType === "triple") {
259 | single = false;
260 | double = false;
261 | triple = true;
262 | }
263 | const data = {
264 | type: "bond",
265 | atom1Name: bondData.bondFrom,
266 | atom2Name: bondData.bondTo,
267 | isSingleBond: single,
268 | isDoubleBond: double,
269 | isTripleBond: triple,
270 | };
271 | handleDataFromEditMolecule(data);
272 | };
273 |
274 | return (
275 |
276 |
277 |
283 | }
285 | aria-controls="panel1-content"
286 | id="panel1-header"
287 | >
288 | ADD ATOMS
289 |
290 |
291 |
340 |
341 |
342 |
343 |
347 | }
349 | aria-controls="panel2-content"
350 | id="panel2-header"
351 | sx={{ backgroundColor: "#2ABD91" }}
352 | >
353 | ADD BONDS
354 |
355 |
356 |
418 |
419 |
420 |
421 |
426 | }
428 | aria-controls="panel3-content"
429 | id="panel3-header"
430 | >
431 | EXAMPLES
432 |
433 |
434 | setSelectedMolecule(e.target.value)}
437 | sx={{ color: "white" }}
438 | >
439 | {sampleMolecules.map((item) => (
440 |
441 | {item}
442 |
443 | ))}
444 |
445 |
446 |
451 | SHOW MOLECULE
452 |
453 |
458 | CLEAR MOLECULE
459 |
460 |
461 |
462 |
463 |
464 | Check Cycle: {checkCycle(molecule) ? "Cyclic" : "Acyclic"}
465 |
466 |
467 |
472 | CLEAR MOLECULE
473 |
474 |
475 |
476 |
477 | );
478 | }
479 |
--------------------------------------------------------------------------------
/molecule-visualiser/src/components/Molecule.js:
--------------------------------------------------------------------------------
1 | import { Molecule, atomNode } from "./GraphADT.js";
2 |
3 | export function createAtomNode(atomName, hybridisation, atomSymbol) {
4 | return new atomNode(atomName, hybridisation, atomSymbol);
5 | }
6 |
7 | export function addAtoms(molecule, atom) {
8 | molecule.addAtoms(atom);
9 | }
10 |
11 | export function addBonds(molecule, atom1Name, atom2Name, isSingleBond, isDoubleBond, isTripleBond) {
12 | const atom1 = molecule.atomList.find((atom) => atom.atomName === atom1Name);
13 | const atom2 = molecule.atomList.find((atom) => atom.atomName === atom2Name);
14 |
15 | if (atom1 && atom2) {
16 | molecule.addBond(atom1, atom2, isSingleBond, isDoubleBond, isTripleBond);
17 | } else {
18 | console.log("One or both atoms not found in molecule.");
19 | }
20 | }
21 |
22 | export function findCentralAtoms(molecule) {
23 | let centralAtoms = [];
24 | let maxHybridisation = "";
25 |
26 | // Find the maximum hybridisation
27 | for (let atom of molecule.atomList) {
28 | if (atom.hybridisation > maxHybridisation) {
29 | maxHybridisation = atom.hybridisation;
30 | }
31 | }
32 |
33 | // Find atoms with maximum hybridisation
34 | for (let atom of molecule.atomList) {
35 | if (atom.hybridisation === maxHybridisation) {
36 | centralAtoms.push(atom);
37 | }
38 | }
39 | return centralAtoms;
40 | }
41 |
42 | // Calculate Coordiantes of the atoms in 3D Plane.
43 | export function getCoordinates(molecule) {
44 | molecule.atomList.forEach(atom => {
45 | atom.bondsAssignedCount = 0;
46 | });
47 |
48 | // Constant Angles defined for respective hybridisations.
49 | const angles = {
50 | sp: { angleX: Math.PI, angleY: 0, angleZ: 0 },
51 | sp2: { angleX: -Math.PI / 6, angleY: Math.PI / 3, angleZ: 0 },
52 | sp3: {
53 | angleX: 0.615 * Math.PI,
54 | angleY: 0.955 * Math.PI,
55 | angleZ: 0.615 * Math.PI,
56 | },
57 | sp3d: {
58 | angleX: Math.PI / 2,
59 | angleY: Math.PI / 2,
60 | angleZ: Math.PI / 2,
61 | },
62 | sp3d2: {
63 | angleX: Math.PI / 2,
64 | angleY: Math.PI / 2,
65 | angleZ: Math.PI / 2
66 | }
67 | };
68 |
69 | // Get Central Atoms of the Molecule.
70 | const centralAtoms = findCentralAtoms(molecule);
71 | let visited = [];
72 | let centralVisited = [];
73 | for (let atom of centralAtoms) {
74 | centralVisited[atom] = false;
75 | }
76 | let queue = [];
77 | let initialAtom = centralAtoms[0];
78 | queue.push([initialAtom, null]);
79 | visited.push(initialAtom);
80 | let directionVectorStack = [];
81 |
82 | while (queue.length) {
83 | let [currentAtom, parentAtom] = queue.shift();
84 | let neighbours = molecule.getNeighbours(currentAtom);
85 | if (currentAtom) {
86 | if (parentAtom === null) {
87 | // Set coordinates for the first atom
88 | currentAtom.coordinates = [0, 0, 0];
89 | directionVectorStack.push([1, 0, 0]);
90 | } else {
91 | let initalDirection = directionVectorStack[directionVectorStack.length - 1];
92 | console.log("Initial Direction: ", initalDirection);
93 | let parentCoordinates = parentAtom.coordinates;
94 | let angleX, angleY, angleZ;
95 | let bondlength = 1;
96 | let newDirection = [];
97 |
98 | angleX = angles[parentAtom.hybridisation].angleX;
99 | angleY = angles[parentAtom.hybridisation].angleY;
100 | angleZ = angles[parentAtom.hybridisation].angleZ;
101 |
102 | if (parentAtom.hybridisation === "sp") {
103 | newDirection = [
104 | -initalDirection[0],
105 | initalDirection[1],
106 | initalDirection[2],
107 | ];
108 | directionVectorStack.push(newDirection);
109 |
110 | let newCoordinates = [
111 | parentCoordinates[0] + bondlength * newDirection[0],
112 | parentCoordinates[1] + bondlength * newDirection[1],
113 | parentCoordinates[2] + bondlength * newDirection[2],
114 | ];
115 | currentAtom.coordinates = newCoordinates;
116 | console.log("New Direction for atom: ", currentAtom, newDirection);
117 | } else if (parentAtom.hybridisation === "sp3d") {
118 | const allNeighbors = molecule.getNeighbours(parentAtom);
119 | const connectionIndex = allNeighbors.findIndex(
120 | atom => atom.atomName === currentAtom.atomName
121 | );
122 |
123 | if (connectionIndex !== -1) {
124 | const newCoordinates = calculateSp3dCoordinates(
125 | parentCoordinates,
126 | connectionIndex,
127 | allNeighbors.length,
128 | 1
129 | );
130 |
131 | currentAtom.coordinates = newCoordinates;
132 |
133 | const newDirection = [
134 | newCoordinates[0] - parentCoordinates[0],
135 | newCoordinates[1] - parentCoordinates[1],
136 | newCoordinates[2] - parentCoordinates[2]
137 | ];
138 |
139 | const magnitude = Math.sqrt(
140 | newDirection[0] * newDirection[0] +
141 | newDirection[1] * newDirection[1] +
142 | newDirection[2] * newDirection[2]
143 | );
144 |
145 | if (magnitude > 0) {
146 | newDirection[0] /= magnitude;
147 | newDirection[1] /= magnitude;
148 | newDirection[2] /= magnitude;
149 | }
150 |
151 | directionVectorStack.push(newDirection);
152 | }
153 | } else if (parentAtom.hybridisation === "sp3d2") {
154 | const octahedralCoords = generateOctahedralCoordinates(parentCoordinates);
155 |
156 | if (typeof parentAtom.bondsAssignedCount === "undefined") {
157 | parentAtom.bondsAssignedCount = 0;
158 | }
159 |
160 | let positionIndex = parentAtom.bondsAssignedCount;
161 | if (positionIndex >= octahedralCoords.length) {
162 | positionIndex = octahedralCoords.length - 1;
163 | }
164 |
165 | parentAtom.bondsAssignedCount += 1;
166 | currentAtom.coordinates = octahedralCoords[positionIndex];
167 |
168 | newDirection = [
169 | octahedralCoords[positionIndex][0] - parentCoordinates[0],
170 | octahedralCoords[positionIndex][1] - parentCoordinates[1],
171 | octahedralCoords[positionIndex][2] - parentCoordinates[2],
172 | ];
173 |
174 | directionVectorStack.push(newDirection);
175 | } else {
176 | if (
177 | (initalDirection[0] === 1 &&
178 | initalDirection[1] === 0 &&
179 | initalDirection[2] === 0) ||
180 | directionVectorStack.length === 1
181 | ) {
182 | newDirection = [
183 | initalDirection[0] * (Math.cos(angleY) * Math.cos(angleZ)) +
184 | initalDirection[1] * (Math.cos(angleY) * Math.sin(angleZ)) -
185 | initalDirection[2] * Math.sin(angleY),
186 | initalDirection[0] *
187 | (-Math.cos(angleX) * Math.sin(angleZ) +
188 | Math.sin(angleX) * Math.sin(angleY) * Math.cos(angleZ)) +
189 | initalDirection[1] *
190 | (Math.cos(angleX) * Math.cos(angleZ) +
191 | Math.sin(angleX) * Math.sin(angleY) * Math.sin(angleZ)) +
192 | initalDirection[2] * (Math.sin(angleX) * Math.cos(angleY)),
193 | initalDirection[0] *
194 | (Math.sin(angleX) * Math.sin(angleZ) +
195 | Math.cos(angleX) * Math.sin(angleY) * Math.cos(angleZ)) +
196 | initalDirection[1] *
197 | (-Math.sin(angleX) * Math.cos(angleZ) +
198 | Math.cos(angleX) * Math.sin(angleY) * Math.sin(angleZ)) +
199 | initalDirection[2] * (Math.cos(angleX) * Math.cos(angleY)),
200 | ];
201 |
202 | if (
203 | parentAtom.hybridisation === "sp2" &&
204 | currentAtom.atomSymbol === "C"
205 | ) {
206 | newDirection = rotateVectorAroundXYPlane(initalDirection, -120);
207 | } else if (
208 | parentAtom.hybridisation === "sp2" &&
209 | currentAtom.atomSymbol === "H"
210 | ) {
211 | newDirection = rotateVectorAroundXYPlane(initalDirection, 120);
212 | }
213 | if (
214 | initalDirection[0] === 1 &&
215 | initalDirection[1] === 0 &&
216 | initalDirection[2] === 0
217 | )
218 | directionVectorStack.pop();
219 | directionVectorStack.push(newDirection);
220 |
221 | let newCoordinates = [
222 | parentCoordinates[0] + bondlength * newDirection[0],
223 | parentCoordinates[1] + bondlength * newDirection[1],
224 | parentCoordinates[2] + bondlength * newDirection[2],
225 | ];
226 | currentAtom.coordinates = newCoordinates;
227 | console.log("New Direction for atom: ", currentAtom, newDirection);
228 | } else if (directionVectorStack.length === 2) {
229 | console.log("Came Here for Atom: ", currentAtom);
230 | let firstCoordinate =
231 | directionVectorStack[directionVectorStack.length - 1];
232 | let secondCoordinate =
233 | directionVectorStack[directionVectorStack.length - 2];
234 |
235 | console.log("First Coordinate:", firstCoordinate);
236 | console.log("Second Coordinate:", secondCoordinate);
237 |
238 | if (parentAtom.hybridisation === "sp2")
239 | newDirection = calculateVector120DegreesOnPlane(
240 | firstCoordinate,
241 | secondCoordinate,
242 | 120
243 | );
244 | if (parentAtom.hybridisation === "sp3")
245 | newDirection = findThirdCoordinate(
246 | firstCoordinate,
247 | secondCoordinate
248 | );
249 | directionVectorStack.push(newDirection);
250 |
251 | console.log("New Direction:", newDirection);
252 |
253 | // Calculate new coordinates
254 | let newCoordinates = [
255 | parentCoordinates[0] + bondlength * newDirection[0],
256 | parentCoordinates[1] + bondlength * newDirection[1],
257 | parentCoordinates[2] + bondlength * newDirection[2],
258 | ];
259 | console.log("New Coordinates:", newCoordinates);
260 | currentAtom.coordinates = newCoordinates;
261 | } else if (directionVectorStack.length === 3) {
262 | console.log("Came to 3 for atom: ", currentAtom);
263 | let firstCoordinate =
264 | directionVectorStack[directionVectorStack.length - 1];
265 | let secondCoordinate =
266 | directionVectorStack[directionVectorStack.length - 2];
267 | let thirdCoordinate =
268 | directionVectorStack[directionVectorStack.length - 3];
269 |
270 | newDirection = findFourthCoordinate(
271 | firstCoordinate,
272 | secondCoordinate,
273 | thirdCoordinate
274 | );
275 | directionVectorStack.push(newDirection);
276 |
277 | console.log("New Direction: ", newDirection);
278 |
279 | let newCoordinates = [
280 | parentCoordinates[0] + bondlength * newDirection[0],
281 | parentCoordinates[1] + bondlength * newDirection[1],
282 | parentCoordinates[2] + bondlength * newDirection[2],
283 | ];
284 | console.log("New Coordinates:", newCoordinates);
285 | currentAtom.coordinates = newCoordinates;
286 | }
287 | }
288 | }
289 |
290 | // Pushes all neighbours of the currentatom to the queue.
291 | let pushedCentral = false;
292 | for (let neighbour of neighbours) {
293 | if (!visited.includes(neighbour)) {
294 | if (centralAtoms.includes(neighbour)) {
295 | if (!pushedCentral) {
296 | queue.push([neighbour, currentAtom]);
297 | visited.push(neighbour);
298 | }
299 | } else {
300 | queue.push([neighbour, currentAtom]);
301 | visited.push(neighbour);
302 | }
303 | if (centralAtoms.includes(neighbour)) pushedCentral = true;
304 | }
305 | }
306 |
307 | // Alkene Part - Yet has some logic to be implemented
308 | if (
309 | !centralAtoms.includes(currentAtom) &&
310 | currentAtom.atomSymbol !== "H"
311 | ) {
312 | if (
313 | checkCentralVisited(centralVisited, centralAtoms) &&
314 | checkVisited(visited, molecule)
315 | ) {
316 | let secondMaxHybrid = "";
317 | for (let atom of molecule.atomList) {
318 | if (
319 | atom.hybridisation > secondMaxHybrid &&
320 | atom.hybridisation < centralAtoms[0].hybridisation
321 | ) {
322 | secondMaxHybrid = atom.hybridisation;
323 | }
324 | }
325 | console.log("New MAX Hybridisation: ", secondMaxHybrid);
326 | for (let atom of molecule.atomList) {
327 | if (atom.hybridisation === secondMaxHybrid) {
328 | centralAtoms.push(atom);
329 | }
330 | }
331 | }
332 | }
333 |
334 | // Updating the stack to be empty if current atom is a central atom
335 | if (centralAtoms.includes(currentAtom) && parentAtom !== null) {
336 | centralVisited[currentAtom] = true;
337 | let temp = directionVectorStack[directionVectorStack.length - 1];
338 | directionVectorStack = [];
339 | directionVectorStack.push(temp);
340 | }
341 |
342 | // Setting the connections property for the current atom
343 | currentAtom.connections = neighbours;
344 | }
345 | console.log("New Central Atoms: ", centralAtoms);
346 | }
347 | }
348 |
349 | function isAxialPosition(index, totalConnections) {
350 | return index === 0 || index === totalConnections - 1;
351 | }
352 |
353 | function calculateSp3dCoordinates(parentCoordinates, connectionIndex, totalConnections, bondLength = 1) {
354 | if (isAxialPosition(connectionIndex, totalConnections)) {
355 | const zDirection = connectionIndex === 0 ? 1 : -1;
356 | return [
357 | parentCoordinates[0],
358 | parentCoordinates[1],
359 | parentCoordinates[2] + (zDirection * bondLength)
360 | ];
361 | } else {
362 | const equatorialIndex = connectionIndex - 1;
363 | const angle = (2 * Math.PI / 3) * equatorialIndex;
364 |
365 | return [
366 | parentCoordinates[0] + bondLength * Math.cos(angle),
367 | parentCoordinates[1] + bondLength * Math.sin(angle),
368 | parentCoordinates[2]
369 | ];
370 | }
371 | }
372 |
373 | function generateOctahedralCoordinates(parentCoordinates, bondLength = 1) {
374 | const directions = [
375 | [1, 0, 0],
376 | [-1, 0, 0],
377 | [0, 1, 0],
378 | [0, -1, 0],
379 | [0, 0, 1],
380 | [0, 0, -1]
381 | ];
382 |
383 | return directions.map(dir => [
384 | parentCoordinates[0] + bondLength * dir[0],
385 | parentCoordinates[1] + bondLength * dir[1],
386 | parentCoordinates[2] + bondLength * dir[2]
387 | ]);
388 | }
389 |
390 |
391 | // Returns true if central atoms list doesnt have any atom yet to be visited.
392 | function checkCentralVisited(centralVisited, centralAtoms) {
393 | for (let atom of centralAtoms) {
394 | if (!centralVisited.includes(atom)) return true;
395 | }
396 | return false;
397 | }
398 |
399 | // Returns true if any of the atom is still yet to be visited.
400 | function checkVisited(visited, molecule) {
401 | for (let atom of molecule.atomList) {
402 | if (!visited.includes(atom)) return true;
403 | }
404 | return false;
405 | }
406 |
407 | export function drawMolecule(molecule) {}
408 |
409 | // Helper function to calculate the vector sum
410 | function vectorSum(v1, v2) {
411 | return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]];
412 | }
413 |
414 | // Helper function to calculate the magnitude of a vector
415 | function magnitude(v) {
416 | return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
417 | }
418 |
419 | // Helper function to normalize a vector
420 | function normalize(v) {
421 | const mag = magnitude(v);
422 | if (mag === 0) {
423 | return [0, 0, 0];
424 | }
425 | return [v[0] / mag, v[1] / mag, v[2] / mag];
426 | }
427 |
428 | // Helper function to calculate the cross product of two vectors
429 | function crossProduct(v1, v2) {
430 | return [
431 | v1[1] * v2[2] - v1[2] * v2[1],
432 | v1[2] * v2[0] - v1[0] * v2[2],
433 | v1[0] * v2[1] - v1[1] * v2[0],
434 | ];
435 | }
436 |
437 | // Helper function to calculate the vector that is 120 degrees from the two given vectors and lies on the same plane
438 | function calculateVector120DegreesOnPlane(
439 | firstCoordinate,
440 | secondCoordinate,
441 | a
442 | ) {
443 | const normal = crossProduct(firstCoordinate, secondCoordinate); // Calculate the normal vector to the plane formed by the two input vectors
444 | const normalizedNormal = normalize(normal); // Normalize the normal vector
445 | const projectedFirst = [
446 | firstCoordinate[0] - firstCoordinate[0] * normalizedNormal[0],
447 | firstCoordinate[1] - firstCoordinate[1] * normalizedNormal[1],
448 | firstCoordinate[2] - firstCoordinate[2] * normalizedNormal[2],
449 | ]; // Project the first vector onto the plane
450 | const projectedSecond = [
451 | secondCoordinate[0] - secondCoordinate[0] * normalizedNormal[0],
452 | secondCoordinate[1] - secondCoordinate[1] * normalizedNormal[1],
453 | secondCoordinate[2] - secondCoordinate[2] * normalizedNormal[2],
454 | ]; // Project the second vector onto the plane
455 | const sumVector = vectorSum(projectedFirst, projectedSecond); // Calculate the vector sum of the projected vectors
456 | const normalizedSumVector = normalize(sumVector); // Normalize the vector sum
457 | const angle = (a * Math.PI) / 180; // Convert 120 degrees to radians
458 | const rotationMatrix = [
459 | [Math.cos(angle), 0, Math.sin(angle)],
460 | [0, 1, 0],
461 | [-Math.sin(angle), 0, Math.cos(angle)],
462 | ]; // Create a rotation matrix for rotating around the Z-axis by 120 degrees
463 | const vector120Degrees = [
464 | rotationMatrix[0][0] * normalizedSumVector[0] +
465 | rotationMatrix[0][1] * normalizedSumVector[1] +
466 | rotationMatrix[0][2] * normalizedSumVector[2],
467 | rotationMatrix[1][0] * normalizedSumVector[0] +
468 | rotationMatrix[1][1] * normalizedSumVector[1] +
469 | rotationMatrix[1][2] * normalizedSumVector[2],
470 | rotationMatrix[2][0] * normalizedSumVector[0] +
471 | rotationMatrix[2][1] * normalizedSumVector[1] +
472 | rotationMatrix[2][2] * normalizedSumVector[2],
473 | ]; // Rotate the normalized vector sum by 120 degrees using the rotation matrix
474 | return vector120Degrees;
475 | }
476 |
477 | function findThirdCoordinate(v1, v2) {
478 | const norm1 = normalize(v1);
479 | const norm2 = normalize(v2);
480 |
481 | const perpendicular = normalize(crossProduct(norm1, norm2));
482 | const tetrahedralAngle = 109.5 * (Math.PI / 180);
483 |
484 | const dotProduct = norm1[0] * norm2[0] + norm1[1] * norm2[1] + norm1[2] * norm2[2];
485 |
486 | const existingAngle = Math.acos(dotProduct);
487 | const rotationAngle = (tetrahedralAngle - existingAngle) / 2;
488 | const rotationAxis = perpendicular;
489 |
490 | const c = Math.cos(rotationAngle);
491 | const s = Math.sin(rotationAngle);
492 | const t = 1 - c;
493 | const x = rotationAxis[0];
494 | const y = rotationAxis[1];
495 | const z = rotationAxis[2];
496 |
497 | const rotatedVector = [
498 | (t * x * x + c) * norm1[0] + (t * x * y - s * z) * norm1[1] + (t * x * z + s * y) * norm1[2],
499 | (t * x * y + s * z) * norm1[0] + (t * y * y + c) * norm1[1] + (t * y * z - s * x) * norm1[2],
500 | (t * x * z - s * y) * norm1[0] + (t * y * z + s * x) * norm1[1] + (t * z * z + c) * norm1[2]
501 | ];
502 |
503 | const thirdVector = normalize([
504 | rotatedVector[0] + perpendicular[0] * Math.sin(tetrahedralAngle),
505 | rotatedVector[1] + perpendicular[1] * Math.sin(tetrahedralAngle),
506 | rotatedVector[2] + perpendicular[2] * Math.sin(tetrahedralAngle)
507 | ]);
508 |
509 | return thirdVector;
510 | }
511 |
512 | function findFourthCoordinate(v1, v2, v3) {
513 | const norm1 = normalize(v1);
514 | const norm2 = normalize(v2);
515 | const norm3 = normalize(v3);
516 |
517 | const sum = [
518 | norm1[0] + norm2[0] + norm3[0],
519 | norm1[1] + norm2[1] + norm3[1],
520 | norm1[2] + norm2[2] + norm3[2]
521 | ];
522 |
523 | const fourthVector = normalize([-sum[0], -sum[1], -sum[2]]);
524 |
525 | return fourthVector;
526 | }
527 |
528 | function rotateVectorAroundXYPlane(vector, angleInDegrees) {
529 | const [x, y, z] = vector;
530 | const angleInRadians = (angleInDegrees * Math.PI) / 180;
531 | const rotatedX = x * Math.cos(angleInRadians) - z * Math.sin(angleInRadians);
532 | const rotatedY = y;
533 | const rotatedZ = x * Math.sin(angleInRadians) + z * Math.cos(angleInRadians);
534 | return [rotatedX, rotatedY, rotatedZ];
535 | }
536 |
537 | function CalcThirdCoordinate(coord1, coord2, angle1, angle2) {
538 | // Convert angles to radians
539 | angle1 = (angle1 * Math.PI) / 180;
540 | angle2 = (angle2 * Math.PI) / 180;
541 |
542 | // Calculate the direction vectors
543 | const dir1 = [Math.cos(angle1), Math.sin(angle1), 0];
544 | const dir2 = [Math.cos(angle2), Math.sin(angle2), 0];
545 |
546 | // Calculate the coefficients for the system of linear equations
547 | const a11 = dir1[0];
548 | const a12 = dir2[0];
549 | const a21 = dir1[1];
550 | const a22 = dir2[1];
551 | const b1 = coord1[0] - coord2[0];
552 | const b2 = coord1[1] - coord2[1];
553 |
554 | // Solve the system of linear equations
555 | const det = a11 * a22 - a12 * a21;
556 | const x = (b1 * a22 - b2 * a12) / det;
557 | const y = (a11 * b2 - a21 * b1) / det;
558 |
559 | // Return the third coordinate
560 | return [x + coord2[0], y + coord2[1], coord2[2]];
561 | }
562 |
--------------------------------------------------------------------------------
/Backend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "backend",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@google/generative-ai": "^0.11.3",
13 | "cors": "^2.8.5",
14 | "dotenv": "^16.4.5",
15 | "express": "^4.19.2",
16 | "nodemon": "^3.1.0"
17 | }
18 | },
19 | "node_modules/@google/generative-ai": {
20 | "version": "0.11.3",
21 | "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.11.3.tgz",
22 | "integrity": "sha512-QtQ1hz6rcybbw35uxXlFF26KNnaTVr2oWwnmDkC1M35KdzN4tVc4wakgJp8uXbY9KDCNHksyp11DbFg0HPckZQ==",
23 | "engines": {
24 | "node": ">=18.0.0"
25 | }
26 | },
27 | "node_modules/accepts": {
28 | "version": "1.3.8",
29 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
30 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
31 | "dependencies": {
32 | "mime-types": "~2.1.34",
33 | "negotiator": "0.6.3"
34 | },
35 | "engines": {
36 | "node": ">= 0.6"
37 | }
38 | },
39 | "node_modules/anymatch": {
40 | "version": "3.1.3",
41 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
42 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
43 | "dependencies": {
44 | "normalize-path": "^3.0.0",
45 | "picomatch": "^2.0.4"
46 | },
47 | "engines": {
48 | "node": ">= 8"
49 | }
50 | },
51 | "node_modules/array-flatten": {
52 | "version": "1.1.1",
53 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
54 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
55 | },
56 | "node_modules/balanced-match": {
57 | "version": "1.0.2",
58 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
59 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
60 | },
61 | "node_modules/binary-extensions": {
62 | "version": "2.3.0",
63 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
64 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
65 | "engines": {
66 | "node": ">=8"
67 | },
68 | "funding": {
69 | "url": "https://github.com/sponsors/sindresorhus"
70 | }
71 | },
72 | "node_modules/body-parser": {
73 | "version": "1.20.2",
74 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
75 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
76 | "dependencies": {
77 | "bytes": "3.1.2",
78 | "content-type": "~1.0.5",
79 | "debug": "2.6.9",
80 | "depd": "2.0.0",
81 | "destroy": "1.2.0",
82 | "http-errors": "2.0.0",
83 | "iconv-lite": "0.4.24",
84 | "on-finished": "2.4.1",
85 | "qs": "6.11.0",
86 | "raw-body": "2.5.2",
87 | "type-is": "~1.6.18",
88 | "unpipe": "1.0.0"
89 | },
90 | "engines": {
91 | "node": ">= 0.8",
92 | "npm": "1.2.8000 || >= 1.4.16"
93 | }
94 | },
95 | "node_modules/body-parser/node_modules/debug": {
96 | "version": "2.6.9",
97 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
98 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
99 | "dependencies": {
100 | "ms": "2.0.0"
101 | }
102 | },
103 | "node_modules/body-parser/node_modules/ms": {
104 | "version": "2.0.0",
105 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
106 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
107 | },
108 | "node_modules/brace-expansion": {
109 | "version": "1.1.11",
110 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
111 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
112 | "dependencies": {
113 | "balanced-match": "^1.0.0",
114 | "concat-map": "0.0.1"
115 | }
116 | },
117 | "node_modules/braces": {
118 | "version": "3.0.2",
119 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
120 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
121 | "dependencies": {
122 | "fill-range": "^7.0.1"
123 | },
124 | "engines": {
125 | "node": ">=8"
126 | }
127 | },
128 | "node_modules/bytes": {
129 | "version": "3.1.2",
130 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
131 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
132 | "engines": {
133 | "node": ">= 0.8"
134 | }
135 | },
136 | "node_modules/call-bind": {
137 | "version": "1.0.7",
138 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
139 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
140 | "dependencies": {
141 | "es-define-property": "^1.0.0",
142 | "es-errors": "^1.3.0",
143 | "function-bind": "^1.1.2",
144 | "get-intrinsic": "^1.2.4",
145 | "set-function-length": "^1.2.1"
146 | },
147 | "engines": {
148 | "node": ">= 0.4"
149 | },
150 | "funding": {
151 | "url": "https://github.com/sponsors/ljharb"
152 | }
153 | },
154 | "node_modules/chokidar": {
155 | "version": "3.6.0",
156 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
157 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
158 | "dependencies": {
159 | "anymatch": "~3.1.2",
160 | "braces": "~3.0.2",
161 | "glob-parent": "~5.1.2",
162 | "is-binary-path": "~2.1.0",
163 | "is-glob": "~4.0.1",
164 | "normalize-path": "~3.0.0",
165 | "readdirp": "~3.6.0"
166 | },
167 | "engines": {
168 | "node": ">= 8.10.0"
169 | },
170 | "funding": {
171 | "url": "https://paulmillr.com/funding/"
172 | },
173 | "optionalDependencies": {
174 | "fsevents": "~2.3.2"
175 | }
176 | },
177 | "node_modules/concat-map": {
178 | "version": "0.0.1",
179 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
180 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
181 | },
182 | "node_modules/content-disposition": {
183 | "version": "0.5.4",
184 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
185 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
186 | "dependencies": {
187 | "safe-buffer": "5.2.1"
188 | },
189 | "engines": {
190 | "node": ">= 0.6"
191 | }
192 | },
193 | "node_modules/content-type": {
194 | "version": "1.0.5",
195 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
196 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
197 | "engines": {
198 | "node": ">= 0.6"
199 | }
200 | },
201 | "node_modules/cookie": {
202 | "version": "0.6.0",
203 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
204 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
205 | "engines": {
206 | "node": ">= 0.6"
207 | }
208 | },
209 | "node_modules/cookie-signature": {
210 | "version": "1.0.6",
211 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
212 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
213 | },
214 | "node_modules/cors": {
215 | "version": "2.8.5",
216 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
217 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
218 | "dependencies": {
219 | "object-assign": "^4",
220 | "vary": "^1"
221 | },
222 | "engines": {
223 | "node": ">= 0.10"
224 | }
225 | },
226 | "node_modules/debug": {
227 | "version": "4.3.4",
228 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
229 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
230 | "dependencies": {
231 | "ms": "2.1.2"
232 | },
233 | "engines": {
234 | "node": ">=6.0"
235 | },
236 | "peerDependenciesMeta": {
237 | "supports-color": {
238 | "optional": true
239 | }
240 | }
241 | },
242 | "node_modules/define-data-property": {
243 | "version": "1.1.4",
244 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
245 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
246 | "dependencies": {
247 | "es-define-property": "^1.0.0",
248 | "es-errors": "^1.3.0",
249 | "gopd": "^1.0.1"
250 | },
251 | "engines": {
252 | "node": ">= 0.4"
253 | },
254 | "funding": {
255 | "url": "https://github.com/sponsors/ljharb"
256 | }
257 | },
258 | "node_modules/depd": {
259 | "version": "2.0.0",
260 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
261 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
262 | "engines": {
263 | "node": ">= 0.8"
264 | }
265 | },
266 | "node_modules/destroy": {
267 | "version": "1.2.0",
268 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
269 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
270 | "engines": {
271 | "node": ">= 0.8",
272 | "npm": "1.2.8000 || >= 1.4.16"
273 | }
274 | },
275 | "node_modules/dotenv": {
276 | "version": "16.4.5",
277 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
278 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
279 | "engines": {
280 | "node": ">=12"
281 | },
282 | "funding": {
283 | "url": "https://dotenvx.com"
284 | }
285 | },
286 | "node_modules/ee-first": {
287 | "version": "1.1.1",
288 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
289 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
290 | },
291 | "node_modules/encodeurl": {
292 | "version": "1.0.2",
293 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
294 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
295 | "engines": {
296 | "node": ">= 0.8"
297 | }
298 | },
299 | "node_modules/es-define-property": {
300 | "version": "1.0.0",
301 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
302 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
303 | "dependencies": {
304 | "get-intrinsic": "^1.2.4"
305 | },
306 | "engines": {
307 | "node": ">= 0.4"
308 | }
309 | },
310 | "node_modules/es-errors": {
311 | "version": "1.3.0",
312 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
313 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
314 | "engines": {
315 | "node": ">= 0.4"
316 | }
317 | },
318 | "node_modules/escape-html": {
319 | "version": "1.0.3",
320 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
321 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
322 | },
323 | "node_modules/etag": {
324 | "version": "1.8.1",
325 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
326 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
327 | "engines": {
328 | "node": ">= 0.6"
329 | }
330 | },
331 | "node_modules/express": {
332 | "version": "4.19.2",
333 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
334 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
335 | "dependencies": {
336 | "accepts": "~1.3.8",
337 | "array-flatten": "1.1.1",
338 | "body-parser": "1.20.2",
339 | "content-disposition": "0.5.4",
340 | "content-type": "~1.0.4",
341 | "cookie": "0.6.0",
342 | "cookie-signature": "1.0.6",
343 | "debug": "2.6.9",
344 | "depd": "2.0.0",
345 | "encodeurl": "~1.0.2",
346 | "escape-html": "~1.0.3",
347 | "etag": "~1.8.1",
348 | "finalhandler": "1.2.0",
349 | "fresh": "0.5.2",
350 | "http-errors": "2.0.0",
351 | "merge-descriptors": "1.0.1",
352 | "methods": "~1.1.2",
353 | "on-finished": "2.4.1",
354 | "parseurl": "~1.3.3",
355 | "path-to-regexp": "0.1.7",
356 | "proxy-addr": "~2.0.7",
357 | "qs": "6.11.0",
358 | "range-parser": "~1.2.1",
359 | "safe-buffer": "5.2.1",
360 | "send": "0.18.0",
361 | "serve-static": "1.15.0",
362 | "setprototypeof": "1.2.0",
363 | "statuses": "2.0.1",
364 | "type-is": "~1.6.18",
365 | "utils-merge": "1.0.1",
366 | "vary": "~1.1.2"
367 | },
368 | "engines": {
369 | "node": ">= 0.10.0"
370 | }
371 | },
372 | "node_modules/express/node_modules/debug": {
373 | "version": "2.6.9",
374 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
375 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
376 | "dependencies": {
377 | "ms": "2.0.0"
378 | }
379 | },
380 | "node_modules/express/node_modules/ms": {
381 | "version": "2.0.0",
382 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
383 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
384 | },
385 | "node_modules/fill-range": {
386 | "version": "7.0.1",
387 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
388 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
389 | "dependencies": {
390 | "to-regex-range": "^5.0.1"
391 | },
392 | "engines": {
393 | "node": ">=8"
394 | }
395 | },
396 | "node_modules/finalhandler": {
397 | "version": "1.2.0",
398 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
399 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
400 | "dependencies": {
401 | "debug": "2.6.9",
402 | "encodeurl": "~1.0.2",
403 | "escape-html": "~1.0.3",
404 | "on-finished": "2.4.1",
405 | "parseurl": "~1.3.3",
406 | "statuses": "2.0.1",
407 | "unpipe": "~1.0.0"
408 | },
409 | "engines": {
410 | "node": ">= 0.8"
411 | }
412 | },
413 | "node_modules/finalhandler/node_modules/debug": {
414 | "version": "2.6.9",
415 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
416 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
417 | "dependencies": {
418 | "ms": "2.0.0"
419 | }
420 | },
421 | "node_modules/finalhandler/node_modules/ms": {
422 | "version": "2.0.0",
423 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
424 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
425 | },
426 | "node_modules/forwarded": {
427 | "version": "0.2.0",
428 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
429 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
430 | "engines": {
431 | "node": ">= 0.6"
432 | }
433 | },
434 | "node_modules/fresh": {
435 | "version": "0.5.2",
436 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
437 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
438 | "engines": {
439 | "node": ">= 0.6"
440 | }
441 | },
442 | "node_modules/fsevents": {
443 | "version": "2.3.3",
444 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
445 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
446 | "hasInstallScript": true,
447 | "optional": true,
448 | "os": [
449 | "darwin"
450 | ],
451 | "engines": {
452 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
453 | }
454 | },
455 | "node_modules/function-bind": {
456 | "version": "1.1.2",
457 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
458 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
459 | "funding": {
460 | "url": "https://github.com/sponsors/ljharb"
461 | }
462 | },
463 | "node_modules/get-intrinsic": {
464 | "version": "1.2.4",
465 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
466 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
467 | "dependencies": {
468 | "es-errors": "^1.3.0",
469 | "function-bind": "^1.1.2",
470 | "has-proto": "^1.0.1",
471 | "has-symbols": "^1.0.3",
472 | "hasown": "^2.0.0"
473 | },
474 | "engines": {
475 | "node": ">= 0.4"
476 | },
477 | "funding": {
478 | "url": "https://github.com/sponsors/ljharb"
479 | }
480 | },
481 | "node_modules/glob-parent": {
482 | "version": "5.1.2",
483 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
484 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
485 | "dependencies": {
486 | "is-glob": "^4.0.1"
487 | },
488 | "engines": {
489 | "node": ">= 6"
490 | }
491 | },
492 | "node_modules/gopd": {
493 | "version": "1.0.1",
494 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
495 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
496 | "dependencies": {
497 | "get-intrinsic": "^1.1.3"
498 | },
499 | "funding": {
500 | "url": "https://github.com/sponsors/ljharb"
501 | }
502 | },
503 | "node_modules/has-flag": {
504 | "version": "3.0.0",
505 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
506 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
507 | "engines": {
508 | "node": ">=4"
509 | }
510 | },
511 | "node_modules/has-property-descriptors": {
512 | "version": "1.0.2",
513 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
514 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
515 | "dependencies": {
516 | "es-define-property": "^1.0.0"
517 | },
518 | "funding": {
519 | "url": "https://github.com/sponsors/ljharb"
520 | }
521 | },
522 | "node_modules/has-proto": {
523 | "version": "1.0.3",
524 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
525 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
526 | "engines": {
527 | "node": ">= 0.4"
528 | },
529 | "funding": {
530 | "url": "https://github.com/sponsors/ljharb"
531 | }
532 | },
533 | "node_modules/has-symbols": {
534 | "version": "1.0.3",
535 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
536 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
537 | "engines": {
538 | "node": ">= 0.4"
539 | },
540 | "funding": {
541 | "url": "https://github.com/sponsors/ljharb"
542 | }
543 | },
544 | "node_modules/hasown": {
545 | "version": "2.0.2",
546 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
547 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
548 | "dependencies": {
549 | "function-bind": "^1.1.2"
550 | },
551 | "engines": {
552 | "node": ">= 0.4"
553 | }
554 | },
555 | "node_modules/http-errors": {
556 | "version": "2.0.0",
557 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
558 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
559 | "dependencies": {
560 | "depd": "2.0.0",
561 | "inherits": "2.0.4",
562 | "setprototypeof": "1.2.0",
563 | "statuses": "2.0.1",
564 | "toidentifier": "1.0.1"
565 | },
566 | "engines": {
567 | "node": ">= 0.8"
568 | }
569 | },
570 | "node_modules/iconv-lite": {
571 | "version": "0.4.24",
572 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
573 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
574 | "dependencies": {
575 | "safer-buffer": ">= 2.1.2 < 3"
576 | },
577 | "engines": {
578 | "node": ">=0.10.0"
579 | }
580 | },
581 | "node_modules/ignore-by-default": {
582 | "version": "1.0.1",
583 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
584 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
585 | },
586 | "node_modules/inherits": {
587 | "version": "2.0.4",
588 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
589 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
590 | },
591 | "node_modules/ipaddr.js": {
592 | "version": "1.9.1",
593 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
594 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
595 | "engines": {
596 | "node": ">= 0.10"
597 | }
598 | },
599 | "node_modules/is-binary-path": {
600 | "version": "2.1.0",
601 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
602 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
603 | "dependencies": {
604 | "binary-extensions": "^2.0.0"
605 | },
606 | "engines": {
607 | "node": ">=8"
608 | }
609 | },
610 | "node_modules/is-extglob": {
611 | "version": "2.1.1",
612 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
613 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
614 | "engines": {
615 | "node": ">=0.10.0"
616 | }
617 | },
618 | "node_modules/is-glob": {
619 | "version": "4.0.3",
620 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
621 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
622 | "dependencies": {
623 | "is-extglob": "^2.1.1"
624 | },
625 | "engines": {
626 | "node": ">=0.10.0"
627 | }
628 | },
629 | "node_modules/is-number": {
630 | "version": "7.0.0",
631 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
632 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
633 | "engines": {
634 | "node": ">=0.12.0"
635 | }
636 | },
637 | "node_modules/media-typer": {
638 | "version": "0.3.0",
639 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
640 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
641 | "engines": {
642 | "node": ">= 0.6"
643 | }
644 | },
645 | "node_modules/merge-descriptors": {
646 | "version": "1.0.1",
647 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
648 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
649 | },
650 | "node_modules/methods": {
651 | "version": "1.1.2",
652 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
653 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
654 | "engines": {
655 | "node": ">= 0.6"
656 | }
657 | },
658 | "node_modules/mime": {
659 | "version": "1.6.0",
660 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
661 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
662 | "bin": {
663 | "mime": "cli.js"
664 | },
665 | "engines": {
666 | "node": ">=4"
667 | }
668 | },
669 | "node_modules/mime-db": {
670 | "version": "1.52.0",
671 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
672 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
673 | "engines": {
674 | "node": ">= 0.6"
675 | }
676 | },
677 | "node_modules/mime-types": {
678 | "version": "2.1.35",
679 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
680 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
681 | "dependencies": {
682 | "mime-db": "1.52.0"
683 | },
684 | "engines": {
685 | "node": ">= 0.6"
686 | }
687 | },
688 | "node_modules/minimatch": {
689 | "version": "3.1.2",
690 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
691 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
692 | "dependencies": {
693 | "brace-expansion": "^1.1.7"
694 | },
695 | "engines": {
696 | "node": "*"
697 | }
698 | },
699 | "node_modules/ms": {
700 | "version": "2.1.2",
701 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
702 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
703 | },
704 | "node_modules/negotiator": {
705 | "version": "0.6.3",
706 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
707 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
708 | "engines": {
709 | "node": ">= 0.6"
710 | }
711 | },
712 | "node_modules/nodemon": {
713 | "version": "3.1.0",
714 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
715 | "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
716 | "dependencies": {
717 | "chokidar": "^3.5.2",
718 | "debug": "^4",
719 | "ignore-by-default": "^1.0.1",
720 | "minimatch": "^3.1.2",
721 | "pstree.remy": "^1.1.8",
722 | "semver": "^7.5.3",
723 | "simple-update-notifier": "^2.0.0",
724 | "supports-color": "^5.5.0",
725 | "touch": "^3.1.0",
726 | "undefsafe": "^2.0.5"
727 | },
728 | "bin": {
729 | "nodemon": "bin/nodemon.js"
730 | },
731 | "engines": {
732 | "node": ">=10"
733 | },
734 | "funding": {
735 | "type": "opencollective",
736 | "url": "https://opencollective.com/nodemon"
737 | }
738 | },
739 | "node_modules/normalize-path": {
740 | "version": "3.0.0",
741 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
742 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
743 | "engines": {
744 | "node": ">=0.10.0"
745 | }
746 | },
747 | "node_modules/object-assign": {
748 | "version": "4.1.1",
749 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
750 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
751 | "engines": {
752 | "node": ">=0.10.0"
753 | }
754 | },
755 | "node_modules/object-inspect": {
756 | "version": "1.13.1",
757 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
758 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
759 | "funding": {
760 | "url": "https://github.com/sponsors/ljharb"
761 | }
762 | },
763 | "node_modules/on-finished": {
764 | "version": "2.4.1",
765 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
766 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
767 | "dependencies": {
768 | "ee-first": "1.1.1"
769 | },
770 | "engines": {
771 | "node": ">= 0.8"
772 | }
773 | },
774 | "node_modules/parseurl": {
775 | "version": "1.3.3",
776 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
777 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
778 | "engines": {
779 | "node": ">= 0.8"
780 | }
781 | },
782 | "node_modules/path-to-regexp": {
783 | "version": "0.1.7",
784 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
785 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
786 | },
787 | "node_modules/picomatch": {
788 | "version": "2.3.1",
789 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
790 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
791 | "engines": {
792 | "node": ">=8.6"
793 | },
794 | "funding": {
795 | "url": "https://github.com/sponsors/jonschlinkert"
796 | }
797 | },
798 | "node_modules/proxy-addr": {
799 | "version": "2.0.7",
800 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
801 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
802 | "dependencies": {
803 | "forwarded": "0.2.0",
804 | "ipaddr.js": "1.9.1"
805 | },
806 | "engines": {
807 | "node": ">= 0.10"
808 | }
809 | },
810 | "node_modules/pstree.remy": {
811 | "version": "1.1.8",
812 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
813 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
814 | },
815 | "node_modules/qs": {
816 | "version": "6.11.0",
817 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
818 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
819 | "dependencies": {
820 | "side-channel": "^1.0.4"
821 | },
822 | "engines": {
823 | "node": ">=0.6"
824 | },
825 | "funding": {
826 | "url": "https://github.com/sponsors/ljharb"
827 | }
828 | },
829 | "node_modules/range-parser": {
830 | "version": "1.2.1",
831 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
832 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
833 | "engines": {
834 | "node": ">= 0.6"
835 | }
836 | },
837 | "node_modules/raw-body": {
838 | "version": "2.5.2",
839 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
840 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
841 | "dependencies": {
842 | "bytes": "3.1.2",
843 | "http-errors": "2.0.0",
844 | "iconv-lite": "0.4.24",
845 | "unpipe": "1.0.0"
846 | },
847 | "engines": {
848 | "node": ">= 0.8"
849 | }
850 | },
851 | "node_modules/readdirp": {
852 | "version": "3.6.0",
853 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
854 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
855 | "dependencies": {
856 | "picomatch": "^2.2.1"
857 | },
858 | "engines": {
859 | "node": ">=8.10.0"
860 | }
861 | },
862 | "node_modules/safe-buffer": {
863 | "version": "5.2.1",
864 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
865 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
866 | "funding": [
867 | {
868 | "type": "github",
869 | "url": "https://github.com/sponsors/feross"
870 | },
871 | {
872 | "type": "patreon",
873 | "url": "https://www.patreon.com/feross"
874 | },
875 | {
876 | "type": "consulting",
877 | "url": "https://feross.org/support"
878 | }
879 | ]
880 | },
881 | "node_modules/safer-buffer": {
882 | "version": "2.1.2",
883 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
884 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
885 | },
886 | "node_modules/semver": {
887 | "version": "7.6.2",
888 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
889 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
890 | "bin": {
891 | "semver": "bin/semver.js"
892 | },
893 | "engines": {
894 | "node": ">=10"
895 | }
896 | },
897 | "node_modules/send": {
898 | "version": "0.18.0",
899 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
900 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
901 | "dependencies": {
902 | "debug": "2.6.9",
903 | "depd": "2.0.0",
904 | "destroy": "1.2.0",
905 | "encodeurl": "~1.0.2",
906 | "escape-html": "~1.0.3",
907 | "etag": "~1.8.1",
908 | "fresh": "0.5.2",
909 | "http-errors": "2.0.0",
910 | "mime": "1.6.0",
911 | "ms": "2.1.3",
912 | "on-finished": "2.4.1",
913 | "range-parser": "~1.2.1",
914 | "statuses": "2.0.1"
915 | },
916 | "engines": {
917 | "node": ">= 0.8.0"
918 | }
919 | },
920 | "node_modules/send/node_modules/debug": {
921 | "version": "2.6.9",
922 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
923 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
924 | "dependencies": {
925 | "ms": "2.0.0"
926 | }
927 | },
928 | "node_modules/send/node_modules/debug/node_modules/ms": {
929 | "version": "2.0.0",
930 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
931 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
932 | },
933 | "node_modules/send/node_modules/ms": {
934 | "version": "2.1.3",
935 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
936 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
937 | },
938 | "node_modules/serve-static": {
939 | "version": "1.15.0",
940 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
941 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
942 | "dependencies": {
943 | "encodeurl": "~1.0.2",
944 | "escape-html": "~1.0.3",
945 | "parseurl": "~1.3.3",
946 | "send": "0.18.0"
947 | },
948 | "engines": {
949 | "node": ">= 0.8.0"
950 | }
951 | },
952 | "node_modules/set-function-length": {
953 | "version": "1.2.2",
954 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
955 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
956 | "dependencies": {
957 | "define-data-property": "^1.1.4",
958 | "es-errors": "^1.3.0",
959 | "function-bind": "^1.1.2",
960 | "get-intrinsic": "^1.2.4",
961 | "gopd": "^1.0.1",
962 | "has-property-descriptors": "^1.0.2"
963 | },
964 | "engines": {
965 | "node": ">= 0.4"
966 | }
967 | },
968 | "node_modules/setprototypeof": {
969 | "version": "1.2.0",
970 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
971 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
972 | },
973 | "node_modules/side-channel": {
974 | "version": "1.0.6",
975 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
976 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
977 | "dependencies": {
978 | "call-bind": "^1.0.7",
979 | "es-errors": "^1.3.0",
980 | "get-intrinsic": "^1.2.4",
981 | "object-inspect": "^1.13.1"
982 | },
983 | "engines": {
984 | "node": ">= 0.4"
985 | },
986 | "funding": {
987 | "url": "https://github.com/sponsors/ljharb"
988 | }
989 | },
990 | "node_modules/simple-update-notifier": {
991 | "version": "2.0.0",
992 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
993 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
994 | "dependencies": {
995 | "semver": "^7.5.3"
996 | },
997 | "engines": {
998 | "node": ">=10"
999 | }
1000 | },
1001 | "node_modules/statuses": {
1002 | "version": "2.0.1",
1003 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1004 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1005 | "engines": {
1006 | "node": ">= 0.8"
1007 | }
1008 | },
1009 | "node_modules/supports-color": {
1010 | "version": "5.5.0",
1011 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1012 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1013 | "dependencies": {
1014 | "has-flag": "^3.0.0"
1015 | },
1016 | "engines": {
1017 | "node": ">=4"
1018 | }
1019 | },
1020 | "node_modules/to-regex-range": {
1021 | "version": "5.0.1",
1022 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1023 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1024 | "dependencies": {
1025 | "is-number": "^7.0.0"
1026 | },
1027 | "engines": {
1028 | "node": ">=8.0"
1029 | }
1030 | },
1031 | "node_modules/toidentifier": {
1032 | "version": "1.0.1",
1033 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1034 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1035 | "engines": {
1036 | "node": ">=0.6"
1037 | }
1038 | },
1039 | "node_modules/touch": {
1040 | "version": "3.1.1",
1041 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1042 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1043 | "bin": {
1044 | "nodetouch": "bin/nodetouch.js"
1045 | }
1046 | },
1047 | "node_modules/type-is": {
1048 | "version": "1.6.18",
1049 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1050 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1051 | "dependencies": {
1052 | "media-typer": "0.3.0",
1053 | "mime-types": "~2.1.24"
1054 | },
1055 | "engines": {
1056 | "node": ">= 0.6"
1057 | }
1058 | },
1059 | "node_modules/undefsafe": {
1060 | "version": "2.0.5",
1061 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1062 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
1063 | },
1064 | "node_modules/unpipe": {
1065 | "version": "1.0.0",
1066 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1067 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1068 | "engines": {
1069 | "node": ">= 0.8"
1070 | }
1071 | },
1072 | "node_modules/utils-merge": {
1073 | "version": "1.0.1",
1074 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1075 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1076 | "engines": {
1077 | "node": ">= 0.4.0"
1078 | }
1079 | },
1080 | "node_modules/vary": {
1081 | "version": "1.1.2",
1082 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1083 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1084 | "engines": {
1085 | "node": ">= 0.8"
1086 | }
1087 | }
1088 | }
1089 | }
1090 |
--------------------------------------------------------------------------------