refactor(build): migrate from Create React App to Vite
Major build system migration replacing react-scripts with Vite. Upgrades React to v19, Redux Toolkit to v2, Three.js to 0.184, and replaces axios with ky. Removes IE11 support, test infrastructure, and polyfills. Updates TypeScript config to project references and bumps version to 3.0.0.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
---
|
||||
description: Analyze repository changes, gather contextual intent from the current codebase status, and generate structurally compliant Git commit messages.
|
||||
metadata:
|
||||
github-path: skills/git-commit
|
||||
github-ref: refs/heads/main
|
||||
github-repo: https://github.com/orrisroot/agent-skills
|
||||
github-tree-sha: 661413ef1afaece6071d57b938ef050d4862e4d4
|
||||
name: git-commit
|
||||
---
|
||||
# Git Commit Operator
|
||||
|
||||
## 1. Purpose
|
||||
Analyze repository changes, gather contextual intent from the current codebase status, and generate structurally compliant Git commit messages.
|
||||
|
||||
## 2. Goals
|
||||
* **Atomic Evaluation:** Review diffs to ensure changes represent a single, focused utility. Suggest breaking up large, mixed changes into distinct, atomic commits.
|
||||
* **Contextual Analysis:** Cross-reference physical changes with the recent discussion history to understand not just *what* changed, but *why* it changed.
|
||||
* **Executable Output:** Provide ready-to-run terminal commands that apply the correct formatting flags, paragraph separators, and line-wrapping behaviors.
|
||||
|
||||
## 3. Operational Workflow for Commits
|
||||
You must execute the following sequential workflow whenever a commit task is initiated or modifications are targeted for staging:
|
||||
|
||||
### Step 1: Diff and Intent Inspection
|
||||
1. Run `git diff --cached` to evaluate the staged modifications.
|
||||
2. Cross-reference the structural changes against recent code review comments, commit messages, or the active chat context to confirm the primary engineering objective.
|
||||
3. If those sources do not clearly explain the change, ask one concise clarifying question before generating the draft.
|
||||
|
||||
### Step 2: Message Draft Formulation
|
||||
1. Construct the message draft strictly using the type classifications and syntax limits specified in `references/conventional_commits.md`.
|
||||
2. Present the drafted structure to the user for validation, highlighting the assigned commit type and scope.
|
||||
|
||||
### Step 3: Terminal Command Delivery
|
||||
1. Upon user confirmation, output the explicit, single-line terminal command using at most three `-m` flags as specified in the formatting policy.
|
||||
* **CRITICAL:** Use at most one `-m` flag per structural part (Subject, Body, and Footer).
|
||||
* **NEVER** use multiple `-m` flags for individual lines or bullet points within the body. Doing so causes Git to insert unwanted blank lines.
|
||||
* **CRITICAL:** For multi-line body or footer content, you must use a single double-quoted string containing **literal newlines (actual line breaks)** within the command. Do NOT use escape sequences like `\n` (which shell command execution will commit literally) or additional `-m` flags.
|
||||
|
||||
Process each step in order and complete the current step before moving to the next.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Reference: Conventional Commits Guidelines
|
||||
|
||||
When drafting, validating, or executing Git commits, you must strictly adhere to the specification and specific formatting rules defined below.
|
||||
|
||||
## Message Format
|
||||
|
||||
```plain
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
## Allowed Types
|
||||
|
||||
* **feat**: A new feature for the user.
|
||||
* **fix**: A bug fix for the user.
|
||||
* **docs**: Documentation only changes.
|
||||
* **style**: Changes that do not affect the meaning of the code (formatting, missing semi-colons, etc).
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature.
|
||||
* **perf**: A code change that improves performance.
|
||||
* **test**: Adding missing tests or correcting existing tests.
|
||||
* **build**: Changes that affect the build system or external dependencies.
|
||||
* **ci**: Changes to CI configuration files and scripts.
|
||||
* **chore**: Other changes that do not modify src or test files.
|
||||
* **revert**: Reverts a previous commit.
|
||||
|
||||
## Strict Constraints
|
||||
|
||||
1. **Default Message Language**:
|
||||
* **The commit message must be written exclusively in English** unless the user provides explicit, specific instructions to use another language for that particular commit.
|
||||
|
||||
2. **Line Length Limits**:
|
||||
* The subject line (first line) must not exceed 72 characters, ideally 50 characters or less.
|
||||
* **Every single line within the commit message body must also be wrapped at 72 characters or less.** If an explanation runs longer, you must manually insert line breaks to keep each line under the 72-character threshold.
|
||||
|
||||
3. **Part Separation via Multiple `-m` Flags (At Most Three)**:
|
||||
* Use at most one `-m` flag per structural part: the subject line, the body, and the footer. Use at most three `-m` flags in total.
|
||||
* **NEVER split body lines or bullet lists across multiple `-m` flags.** Doing so inserts unwanted blank lines because Git automatically treats each `-m` flag as a separate paragraph.
|
||||
* **DO NOT use escape sequences like `\n` in double quotes.** Shells like bash will treat `\n` as literal backslash-n characters and commit them as-is.
|
||||
* **Use literal newlines (actual line breaks)** inside double quotes to write multi-line bodies or footers.
|
||||
* *Example:*
|
||||
|
||||
```bash
|
||||
git commit -m "feat(auth): add jwt authentication" -m "Validate tokens on every incoming request.
|
||||
Secure endpoints by rejecting expired credentials.
|
||||
- Added middleware for token validation
|
||||
- Removed legacy session handling" -m "BREAKING CHANGE: The old session-based cookie auth is deprecated."
|
||||
```
|
||||
|
||||
|
||||
4. **Co-authored-by Restriction**:
|
||||
* **Do not include any `Co-authored-by:` trailers** in the commit message or footer unless the user explicitly requests you to add credit for a co-author.
|
||||
|
||||
5. **Case and Punctuation**:
|
||||
* The description line must be written in lowercase and must not end with a period.
|
||||
|
||||
6. **Imperative Mood**:
|
||||
* Always use the imperative mood in the description (e.g., use "add", "fix", "change" instead of "added", "fixes", "changed").
|
||||
+26
-13
@@ -1,24 +1,37 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# production
|
||||
dist
|
||||
dist-ssr
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
# Environment files
|
||||
*.local
|
||||
|
||||
# misc
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.eslintcache
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
# Misc
|
||||
.eslintcache
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 120
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"domains": {
|
||||
"react": "recommended"
|
||||
},
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||
"files": {
|
||||
"includes": ["**", "!**/dist", "!**/node_modules"]
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import js from '@eslint/js';
|
||||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -2,18 +2,19 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="CelLoc-3D is a database of the 3D arrangement of neocortical cells (gultamatargic/excitatory, GABAergic/inhibitory and/or astrocytes) identified in vivo in layer 2/3 of the primary visual cortex of the mouse by two-photon imaging."
|
||||
/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>CelLoc3D Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+3266
File diff suppressed because it is too large
Load Diff
+35
-49
@@ -1,57 +1,43 @@
|
||||
{
|
||||
"name": "celloc3d",
|
||||
"version": "2.0.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.2",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
"@types/node": "^17.0.40",
|
||||
"@types/react": "^18.0.12",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/three": "^0.141.0",
|
||||
"axios": "^0.27.2",
|
||||
"react": "^18.1.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-ga4": "^1.4.1",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-nl2br": "^1.0.4",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"redux": "^4.2.0",
|
||||
"three": "^0.141.0",
|
||||
"typescript": "^4.7.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@svgr/webpack": "^6.2.1"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "biome check --write .",
|
||||
"format:check": "biome check .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.12.0",
|
||||
"ky": "^2.0.2",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-ga4": "^3.0.1",
|
||||
"react-helmet-async": "^3.0.0",
|
||||
"react-redux": "^9.3.0",
|
||||
"react-router-dom": "^7.15.1",
|
||||
"redux": "^5.0.1",
|
||||
"three": "^0.184.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all",
|
||||
"ie 11"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version",
|
||||
"ie 11"
|
||||
]
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/react": "^19.2.15",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"@types/three": "^0.184.1",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"eslint": "^10.4.0",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.6.0",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.59.4",
|
||||
"vite": "^8.0.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
"view": "\/data\/TE0001\/viewer",
|
||||
"mouseLine": "VGAT-Venus transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"Glutamatergic\/Excitatory neurons",
|
||||
"GABAergic\/Inhibitory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["Glutamatergic\/Excitatory neurons", "GABAergic\/Inhibitory neurons", "Astrocytes"],
|
||||
"postnatalDays": 67,
|
||||
"imagingVolumeSize": "317 x 317 x 90 um",
|
||||
"imagingDepth": "from 120 to 210 um",
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
"view": "\/data\/TE0002\/viewer",
|
||||
"mouseLine": "VGAT-Venus transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"Glutamatergic\/Excitatory neurons",
|
||||
"GABAergic\/Inhibitory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["Glutamatergic\/Excitatory neurons", "GABAergic\/Inhibitory neurons", "Astrocytes"],
|
||||
"postnatalDays": 78,
|
||||
"imagingVolumeSize": "317 x 317 x 90 um",
|
||||
"imagingDepth": "from 130 to 220 um",
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
"view": "\/data\/TE0003\/viewer",
|
||||
"mouseLine": "VGAT-Venus transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"Glutamatergic\/Excitatory neurons",
|
||||
"GABAergic\/Inhibitory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["Glutamatergic\/Excitatory neurons", "GABAergic\/Inhibitory neurons", "Astrocytes"],
|
||||
"postnatalDays": 70,
|
||||
"imagingVolumeSize": "317 x 317 x 90 um",
|
||||
"imagingDepth": "from 120 to 225 um",
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
"view": "\/data\/TE0004\/viewer",
|
||||
"mouseLine": "VGAT-Venus transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"Glutamatergic\/Excitatory neurons",
|
||||
"GABAergic\/Inhibitory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["Glutamatergic\/Excitatory neurons", "GABAergic\/Inhibitory neurons", "Astrocytes"],
|
||||
"postnatalDays": 90,
|
||||
"imagingVolumeSize": "317 x 317 x 90 um",
|
||||
"imagingDepth": "from 110 to 200 um",
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
"view": "\/data\/TE0005\/viewer",
|
||||
"mouseLine": "VGAT-Venus transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"Glutamatergic\/Excitatory neurons",
|
||||
"GABAergic\/Inhibitory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["Glutamatergic\/Excitatory neurons", "GABAergic\/Inhibitory neurons", "Astrocytes"],
|
||||
"postnatalDays": 75,
|
||||
"imagingVolumeSize": "317 x 317 x 90 um",
|
||||
"imagingDepth": "from 125 to 230 um",
|
||||
|
||||
@@ -9,12 +9,7 @@
|
||||
"view": "\/data\/TE0006\/viewer",
|
||||
"mouseLine": "PV\/myrGFP-LDLRct transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"PV-positive neurons",
|
||||
"SOM-positive neurons",
|
||||
"Putative excitatory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["PV-positive neurons", "SOM-positive neurons", "Putative excitatory neurons", "Astrocytes"],
|
||||
"postnatalDays": 77,
|
||||
"imagingVolumeSize": "317 x 317 x 105 um",
|
||||
"imagingDepth": "from 110 to 215 um",
|
||||
|
||||
@@ -9,12 +9,7 @@
|
||||
"view": "\/data\/TE0007\/viewer",
|
||||
"mouseLine": "PV/myrGFP-LDLRct transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"PV-positive neurons",
|
||||
"SOM-positive neurons",
|
||||
"Putative excitatory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["PV-positive neurons", "SOM-positive neurons", "Putative excitatory neurons", "Astrocytes"],
|
||||
"postnatalDays": 73,
|
||||
"imagingVolumeSize": "317 x 317 x 105 um",
|
||||
"imagingDepth": "from 110 to 215 um",
|
||||
|
||||
@@ -9,12 +9,7 @@
|
||||
"view": "\/data\/TE0008\/viewer",
|
||||
"mouseLine": "PV/myrGFP-LDLRct transgenic mouse",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"PV-positive neurons",
|
||||
"SOM-positive neurons",
|
||||
"Putative excitatory neurons",
|
||||
"Astrocytes"
|
||||
],
|
||||
"cellType": ["PV-positive neurons", "SOM-positive neurons", "Putative excitatory neurons", "Astrocytes"],
|
||||
"postnatalDays": 70,
|
||||
"imagingVolumeSize": "317 x 317 x 105 um",
|
||||
"imagingDepth": "from 110 to 215 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0009\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 53,
|
||||
"imagingVolumeSize": "317 x 317 x 110 um",
|
||||
"imagingDepth": "from 100 to 210 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0010\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic/inhibitory neurons"],
|
||||
"postnatalDays": 100,
|
||||
"imagingVolumeSize": "512 x 512 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0011\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 70,
|
||||
"imagingVolumeSize": "512 x 512 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0012\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 78,
|
||||
"imagingVolumeSize": "512 x 512 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0013\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 69,
|
||||
"imagingVolumeSize": "512 x 512 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0014\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 44,
|
||||
"imagingVolumeSize": "317 x 317 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0015\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 92,
|
||||
"imagingVolumeSize": "512 x 512 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"view": "\/data\/TE0016\/viewer",
|
||||
"mouseLine": "Dlx5\/6-GCaMP3 mouse (Crossing Dlx5\/6-Flpe and R26-CAG-FRT-GCaMP3 transgenic mice)",
|
||||
"imagingArea": "Layer 2\/3, Primary Visual Cortex",
|
||||
"cellType": [
|
||||
"GABAergic\/inhibitory neurons"
|
||||
],
|
||||
"cellType": ["GABAergic\/inhibitory neurons"],
|
||||
"postnatalDays": 47,
|
||||
"imagingVolumeSize": "512 x 512 x 110 um",
|
||||
"imagingDepth": "from 90 to 200 um",
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
.main {
|
||||
width: 100%;
|
||||
min-height: 330px;
|
||||
height: auto !important;
|
||||
height: auto;
|
||||
border: thin solid #bb3300;
|
||||
text-align: left;
|
||||
border-radius: 20px;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import App from "./App";
|
||||
import { store } from "./app/store";
|
||||
|
||||
test("renders learn react link", () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
+15
-14
@@ -1,20 +1,19 @@
|
||||
import React from "react";
|
||||
import ReactGA from "react-ga4";
|
||||
import { BrowserRouter, NavLink, Route, Routes, useLocation } from "react-router-dom";
|
||||
import styles from "./App.module.css";
|
||||
import CompatRedirect from "./features/compat-redirect/CompatRedirect";
|
||||
import Contact from "./features/contact/Contact";
|
||||
import Data from "./features/data/Data";
|
||||
import Help from "./features/help/Help";
|
||||
import News from "./features/news/News";
|
||||
import PageTitle from "./features/page-title/PageTitle";
|
||||
import Search from "./features/search/Search";
|
||||
import React from 'react';
|
||||
import ReactGA from 'react-ga4';
|
||||
import { BrowserRouter, NavLink, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import styles from './App.module.css';
|
||||
import CompatRedirect from './features/compat-redirect/CompatRedirect';
|
||||
import Contact from './features/contact/Contact';
|
||||
import Data from './features/data/Data';
|
||||
import Help from './features/help/Help';
|
||||
import News from './features/news/News';
|
||||
import PageTitle from './features/page-title/PageTitle';
|
||||
import Search from './features/search/Search';
|
||||
|
||||
const AppMain: React.FC = () => {
|
||||
const location = useLocation();
|
||||
|
||||
React.useEffect(() => {
|
||||
ReactGA.send("pageview");
|
||||
ReactGA.send('pageview');
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
@@ -52,7 +51,9 @@ const AppMain: React.FC = () => {
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
ReactGA.initialize("G-T4L3SDCN6P");
|
||||
React.useEffect(() => {
|
||||
ReactGA.initialize('G-T4L3SDCN6P');
|
||||
}, []);
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<AppMain />
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
import type { AppDispatch, RootState } from "./store";
|
||||
import { type TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
|
||||
import type { AppDispatch, RootState } from './store';
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { Action, configureStore, ThunkAction } from "@reduxjs/toolkit";
|
||||
import pageTitleReducer from "../features/page-title/pageTitleSlice";
|
||||
import searchResultsReducer from "../features/search-results/searchResultsSlice";
|
||||
import { type Action, configureStore, type ThunkAction } from '@reduxjs/toolkit';
|
||||
import pageTitleReducer from '../features/page-title/pageTitleSlice';
|
||||
import searchResultsReducer from '../features/search-results/searchResultsSlice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"recordingVolume": {
|
||||
"width": 317,
|
||||
"height": 317,
|
||||
"depth": 105
|
||||
"depth": 105
|
||||
},
|
||||
"imagingDepth": {
|
||||
"from": 110,
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
import React from "react";
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
import NotFound from "../not-found/NotFound";
|
||||
import type React from 'react';
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
import NotFound from '../not-found/NotFound';
|
||||
|
||||
const CompatRedirect: React.FC = () => {
|
||||
const { pathname } = useLocation();
|
||||
switch (pathname) {
|
||||
case "/index.html":
|
||||
case '/index.html':
|
||||
return <Navigate to="/" />;
|
||||
case "/Search.html":
|
||||
case '/Search.html':
|
||||
return <Navigate to="/search" />;
|
||||
case "/Help.html":
|
||||
case '/Help.html':
|
||||
return <Navigate to="/help" />;
|
||||
case "/Help_S1.html":
|
||||
case '/Help_S1.html':
|
||||
return <Navigate to="/help/search/1" />;
|
||||
case "/Help_S2.html":
|
||||
case '/Help_S2.html':
|
||||
return <Navigate to="/help/search/2" />;
|
||||
case "/Help_S3.html":
|
||||
case '/Help_S3.html':
|
||||
return <Navigate to="/help/search/3" />;
|
||||
case "/Help_S4.html":
|
||||
case '/Help_S4.html':
|
||||
return <Navigate to="/help/search/4" />;
|
||||
case "/Help_S5.html":
|
||||
case '/Help_S5.html':
|
||||
return <Navigate to="/help/search/5" />;
|
||||
case "/Help_Dataset.html":
|
||||
case '/Help_Dataset.html':
|
||||
return <Navigate to="/help/dataset" />;
|
||||
case "/Help_Viewer.html":
|
||||
case '/Help_Viewer.html':
|
||||
return <Navigate to="/help/viewer" />;
|
||||
case "/Contact.html":
|
||||
case '/Contact.html':
|
||||
return <Navigate to="/help/contact" />;
|
||||
case "/cgi-bin/SearchResult.cgi":
|
||||
case '/cgi-bin/SearchResult.cgi':
|
||||
return <Navigate to="/search/results" />;
|
||||
default:
|
||||
default: {
|
||||
const match = pathname.match(/^\/DB\/TE\/(3D-)?(.+)\.html$/);
|
||||
if (match !== null) {
|
||||
const url = "/data/" + match[2] + (typeof match[1] !== "undefined" ? "/viewer" : "");
|
||||
const url = `/data/${match[2]}${typeof match[1] !== 'undefined' ? '/viewer' : ''}`;
|
||||
return <Navigate to={url} />;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return <NotFound />;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./Contact.module.css";
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './Contact.module.css';
|
||||
|
||||
const Contact: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle("Contact Information"));
|
||||
dispatch(setTitle('Contact Information'));
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div className={styles.contact}>
|
||||
<section className={styles.notice}>Notice: This site has been archived since May 2019 and is no longer updated.</section>
|
||||
<section className={styles.notice}>
|
||||
Notice: This site has been archived since May 2019 and is no longer updated.
|
||||
</section>
|
||||
<h4>CelLoc-3D is managed by Teppei Ebina and Tadaharu Tsumoto</h4>
|
||||
<p>
|
||||
Laboratory for cortical circuit plasticity
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import axios from "axios";
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import DataUtils from "../data/DataUtils";
|
||||
import Loading from "../loading/Loading";
|
||||
import NotFound from "../not-found/NotFound";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./DataDetail.module.css";
|
||||
import ky from 'ky';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import DataUtils from '../data/DataUtils';
|
||||
import Loading from '../loading/Loading';
|
||||
import NotFound from '../not-found/NotFound';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './DataDetail.module.css';
|
||||
|
||||
interface DetailDataJson {
|
||||
title: string;
|
||||
@@ -28,129 +28,110 @@ interface DetailDataJson {
|
||||
releaseDate: string;
|
||||
}
|
||||
|
||||
interface PropsFC {
|
||||
interface Props {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Props extends PropsFC {
|
||||
dispatch: any;
|
||||
}
|
||||
const DataDetail: React.FC<Props> = ({ name }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [data, setData] = useState<DetailDataJson | null>(null);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
interface State {
|
||||
name: string;
|
||||
data: DetailDataJson | null;
|
||||
}
|
||||
useEffect(() => {
|
||||
let isActive = true;
|
||||
|
||||
class DataDetail extends Component<Props, State> {
|
||||
isActive = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: props.name,
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { name } = this.state;
|
||||
this.isActive = true;
|
||||
if (!DataUtils.exists(name)) {
|
||||
if (name !== "") {
|
||||
this.setState({ name: "" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`/data/${name}.json`, { responseType: "json" })
|
||||
.then((response) => {
|
||||
if (this.isActive) {
|
||||
this.props.dispatch(setTitle(`DS: ${name}`));
|
||||
this.setState({ data: response.data as DetailDataJson });
|
||||
|
||||
setData(null);
|
||||
setError(false);
|
||||
|
||||
ky.get(`/data/${name}.json`)
|
||||
.json<DetailDataJson>()
|
||||
.then((result) => {
|
||||
if (isActive) {
|
||||
dispatch(setTitle(`DS: ${name}`));
|
||||
setData(result);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (this.isActive) {
|
||||
this.setState({ name: "" });
|
||||
.catch(() => {
|
||||
if (isActive) {
|
||||
setError(true);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
isActive = false;
|
||||
};
|
||||
}, [name, dispatch]);
|
||||
|
||||
if (name === '' || error) {
|
||||
return <NotFound />;
|
||||
}
|
||||
if (!data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, data } = this.state;
|
||||
if (name === "") {
|
||||
return <NotFound />;
|
||||
}
|
||||
if (!data) {
|
||||
return <Loading />;
|
||||
}
|
||||
return (
|
||||
<section className={styles.detail}>
|
||||
<h4 className={styles.title}>{data.title}</h4>
|
||||
<div className={styles.author}>{data.author}</div>
|
||||
<figure>
|
||||
<img src={data.figure.file} alt={data.figure.caption} />
|
||||
<figcaption>{data.figure.caption}</figcaption>
|
||||
</figure>
|
||||
<dl>
|
||||
<dt>Download & View</dt>
|
||||
<dd>
|
||||
<a href={data.download}>Text download</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<Link to={data.view}>View 3D structure</Link>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Mouse line</dt>
|
||||
<dd>{data.mouseLine}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Imaging area</dt>
|
||||
<dd>{data.imagingArea}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Cell type</dt>
|
||||
{data.cellType.map((value, key) => {
|
||||
return <dd key={key}>{value}</dd>;
|
||||
})}
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Postnatal days</dt>
|
||||
<dd>{data.postnatalDays}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Imaging volume size</dt>
|
||||
<dd>{data.imagingVolumeSize}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Imaging depth</dt>
|
||||
<dd>{data.imagingDepth}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Journal</dt>
|
||||
<dd>{data.journal}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Summary</dt>
|
||||
<dd dangerouslySetInnerHTML={{ __html: data.summary }}></dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Release date</dt>
|
||||
<dd>{data.releaseDate}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const DataDetailFC: React.FC<PropsFC> = (props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
return <DataDetail dispatch={dispatch} name={props.name} />;
|
||||
return (
|
||||
<section className={styles.detail}>
|
||||
<h4 className={styles.title}>{data.title}</h4>
|
||||
<div className={styles.author}>{data.author}</div>
|
||||
<figure>
|
||||
<img src={data.figure.file} alt={data.figure.caption} />
|
||||
<figcaption>{data.figure.caption}</figcaption>
|
||||
</figure>
|
||||
<dl>
|
||||
<dt>Download & View</dt>
|
||||
<dd>
|
||||
<a href={data.download}>Text download</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<Link to={data.view}>View 3D structure</Link>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Mouse line</dt>
|
||||
<dd>{data.mouseLine}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Imaging area</dt>
|
||||
<dd>{data.imagingArea}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Cell type</dt>
|
||||
{data.cellType.map((value, key) => {
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: cell type values may duplicate
|
||||
return <dd key={key}>{value}</dd>;
|
||||
})}
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Postnatal days</dt>
|
||||
<dd>{data.postnatalDays}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Imaging volume size</dt>
|
||||
<dd>{data.imagingVolumeSize}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Imaging depth</dt>
|
||||
<dd>{data.imagingDepth}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Journal</dt>
|
||||
<dd>{data.journal}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Summary</dt>
|
||||
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: summary is trusted content from our own JSON */}
|
||||
<dd dangerouslySetInnerHTML={{ __html: data.summary }}></dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>Release date</dt>
|
||||
<dd>{data.releaseDate}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataDetailFC;
|
||||
export default DataDetail;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import axios from "axios";
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as THREE from "three";
|
||||
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import DataUtils from "../data/DataUtils";
|
||||
import Loading from "../loading/Loading";
|
||||
import NotFound from "../not-found/NotFound";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./DataViewer.module.css";
|
||||
import ky from 'ky';
|
||||
import type React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import * as THREE from 'three';
|
||||
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import DataUtils from '../data/DataUtils';
|
||||
import Loading from '../loading/Loading';
|
||||
import NotFound from '../not-found/NotFound';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './DataViewer.module.css';
|
||||
|
||||
class Viewer3D {
|
||||
private mount: HTMLDivElement;
|
||||
@@ -26,15 +27,15 @@ class Viewer3D {
|
||||
}
|
||||
|
||||
init(data: string) {
|
||||
const lines = data.split("\n");
|
||||
const lines = data.split('\n');
|
||||
const head = lines
|
||||
.shift()
|
||||
?.trim()
|
||||
.split("\t")
|
||||
.map((line, k) => {
|
||||
return parseInt(line.replace(/^#/g, ""), 10) || 0;
|
||||
.split('\t')
|
||||
.map((line) => {
|
||||
return parseInt(line.replace(/^#/g, ''), 10) || 0;
|
||||
});
|
||||
if (typeof head === "undefined" || head.length !== 3 || head.includes(0)) {
|
||||
if (typeof head === 'undefined' || head.length !== 3 || head.includes(0)) {
|
||||
return false;
|
||||
}
|
||||
const width = this.mount.clientWidth;
|
||||
@@ -65,9 +66,9 @@ class Viewer3D {
|
||||
};
|
||||
const geometory = new THREE.SphereGeometry(4, 10, 10);
|
||||
lines.forEach((line) => {
|
||||
const point = line.trim().split("\t");
|
||||
if (point.length === 4 && material.hasOwnProperty(point[0])) {
|
||||
const particle = new THREE.Mesh(geometory, material[point[0] as "e" | "i" | "g" | "p" | "s" | "ps"]);
|
||||
const point = line.trim().split('\t');
|
||||
if (point.length === 4 && Object.hasOwn(material, point[0])) {
|
||||
const particle = new THREE.Mesh(geometory, material[point[0] as 'e' | 'i' | 'g' | 'p' | 's' | 'ps']);
|
||||
particle.position.x = parseFloat(point[1]) - head[0] / 2.0;
|
||||
particle.position.y = parseFloat(point[2]) - head[1] / 2.0;
|
||||
particle.position.z = head[2] - parseFloat(point[3]) * 2.0;
|
||||
@@ -79,7 +80,7 @@ class Viewer3D {
|
||||
this.renderer.setSize(width, height);
|
||||
this.renderer.setClearColor(0xffffff);
|
||||
this.mount.appendChild(this.renderer.domElement);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -90,118 +91,102 @@ class Viewer3D {
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.frameId && cancelAnimationFrame(this.frameId);
|
||||
this.renderer && this.mount.removeChild(this.renderer.domElement);
|
||||
if (this.frameId) cancelAnimationFrame(this.frameId);
|
||||
if (this.renderer && this.mount) this.mount.removeChild(this.renderer.domElement);
|
||||
}
|
||||
|
||||
animate() {
|
||||
this.controls && this.controls.update();
|
||||
this.renderer && this.renderer.clear();
|
||||
this.renderer && this.scene && this.camera && this.renderer.render(this.scene, this.camera);
|
||||
if (this.controls) this.controls.update();
|
||||
if (this.renderer) this.renderer.clear();
|
||||
if (this.renderer && this.scene && this.camera) this.renderer.render(this.scene, this.camera);
|
||||
this.frameId = requestAnimationFrame(this.animate);
|
||||
}
|
||||
}
|
||||
|
||||
interface PropsFC {
|
||||
interface DataViewerProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Props extends PropsFC {
|
||||
dispatch: any;
|
||||
}
|
||||
const DataViewer: React.FC<DataViewerProps> = ({ name: initialName }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [name, setName] = useState(initialName);
|
||||
const [data, setData] = useState('');
|
||||
const mountRef = useRef<HTMLDivElement>(null);
|
||||
const viewerRef = useRef<Viewer3D | null>(null);
|
||||
const isActiveRef = useRef(true);
|
||||
|
||||
interface State {
|
||||
name: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
class DataViewer extends Component<Props, State> {
|
||||
private isActive = false;
|
||||
private mount: HTMLDivElement | null = null;
|
||||
private viewer: Viewer3D | null = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: this.props.name,
|
||||
data: "",
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { name } = this.state;
|
||||
this.isActive = true;
|
||||
useEffect(() => {
|
||||
isActiveRef.current = true;
|
||||
if (!DataUtils.exists(name)) {
|
||||
if (name !== "") {
|
||||
this.setState({ name: "" });
|
||||
if (name !== '') {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional validation reset
|
||||
setName('');
|
||||
}
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`/data/${name}.dat`, { responseType: "text" })
|
||||
.then((response) => {
|
||||
if (this.isActive) {
|
||||
this.props.dispatch(setTitle(`View 3D: ${name}`));
|
||||
this.setState({ data: response.data });
|
||||
const controller = new AbortController();
|
||||
ky.get(`/data/${name}.dat`, { signal: controller.signal })
|
||||
.text()
|
||||
.then((data) => {
|
||||
if (isActiveRef.current) {
|
||||
dispatch(setTitle(`View 3D: ${name}`));
|
||||
setData(data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (this.isActive) {
|
||||
this.setState({ name: "" });
|
||||
.catch(() => {
|
||||
if (isActiveRef.current && !controller.signal.aborted) {
|
||||
setName('');
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [name, dispatch]);
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
if (this.mount && this.state.data !== "" && !this.viewer) {
|
||||
const viewer = new Viewer3D(this.mount);
|
||||
if (viewer.init(this.state.data)) {
|
||||
this.viewer = viewer;
|
||||
this.viewer.start();
|
||||
useEffect(() => {
|
||||
if (mountRef.current && data !== '') {
|
||||
// Stop and clean up the old viewer if switching datasets
|
||||
if (viewerRef.current) {
|
||||
viewerRef.current.stop();
|
||||
viewerRef.current = null;
|
||||
}
|
||||
const viewer = new Viewer3D(mountRef.current);
|
||||
if (viewer.init(data)) {
|
||||
viewerRef.current = viewer;
|
||||
viewerRef.current.start();
|
||||
} else {
|
||||
this.setState({ name: "@@NOWEBGL@@" });
|
||||
setName('@@NOWEBGL@@');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
componentWillUnmount() {
|
||||
this.isActive = false;
|
||||
if (this.viewer) {
|
||||
this.viewer.stop();
|
||||
this.viewer = null;
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isActiveRef.current = false;
|
||||
if (viewerRef.current) {
|
||||
viewerRef.current.stop();
|
||||
viewerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
render() {
|
||||
const { name, data } = this.state;
|
||||
switch (name) {
|
||||
case "":
|
||||
return <NotFound />;
|
||||
case "@@NOWEBGL@@":
|
||||
return <div className={styles.no_webgl}>It does not appear your computer supports WebGL.</div>;
|
||||
}
|
||||
if (data === "") {
|
||||
return <Loading />;
|
||||
}
|
||||
return (
|
||||
<section>
|
||||
<div className={styles.back}>
|
||||
<Link to={`/data/${name}`}>Back to {name}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={styles.viewer}
|
||||
ref={(mount) => {
|
||||
this.mount = mount;
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
switch (name) {
|
||||
case '':
|
||||
return <NotFound />;
|
||||
case '@@NOWEBGL@@':
|
||||
return <div className={styles.no_webgl}>It does not appear your computer supports WebGL.</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const DataViewerFC: React.FC<PropsFC> = (props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
return <DataViewer dispatch={dispatch} name={props.name} />;
|
||||
if (data === '') {
|
||||
return <Loading />;
|
||||
}
|
||||
return (
|
||||
<section>
|
||||
<div className={styles.back}>
|
||||
<Link to={`/data/${name}`}>Back to {name}</Link>
|
||||
</div>
|
||||
<div className={styles.viewer} ref={mountRef} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataViewerFC;
|
||||
export default DataViewer;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
import { Route, Routes, useParams } from "react-router-dom";
|
||||
import DataDetail from "../data-detail/DataDetail";
|
||||
import DataViewer from "../data-viewer/DataViewer";
|
||||
import NotFound from "../not-found/NotFound";
|
||||
import styles from "./Data.module.css";
|
||||
import DataUtils from "./DataUtils";
|
||||
import type React from 'react';
|
||||
import { Route, Routes, useParams } from 'react-router-dom';
|
||||
import DataDetail from '../data-detail/DataDetail';
|
||||
import DataViewer from '../data-viewer/DataViewer';
|
||||
import NotFound from '../not-found/NotFound';
|
||||
import styles from './Data.module.css';
|
||||
import DataUtils from './DataUtils';
|
||||
|
||||
const Data: React.FC = () => {
|
||||
const { name } = useParams<"name">();
|
||||
if (typeof name === "undefined" || !DataUtils.exists(name)) {
|
||||
const { name } = useParams<'name'>();
|
||||
if (typeof name === 'undefined' || !DataUtils.exists(name)) {
|
||||
return <NotFound />;
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import JsonData_ from "../../assets/data.json";
|
||||
import JsonData_ from '../../assets/data.json';
|
||||
|
||||
const cellTypes = ["E", "I", "G", "P", "S"] as const;
|
||||
type TCellType = typeof cellTypes[number];
|
||||
type TCellType = 'E' | 'I' | 'G' | 'P' | 'S';
|
||||
|
||||
interface JsonDataItem {
|
||||
pd: number;
|
||||
@@ -48,42 +47,41 @@ export type SearchResults = SearchResult[];
|
||||
|
||||
const jsonData: JsonData = JsonData_;
|
||||
|
||||
class DataUtils {
|
||||
static exists(name: string): boolean {
|
||||
return jsonData.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
static search(params: SearchParams): SearchResults {
|
||||
const results: SearchResults = [];
|
||||
for (let name in jsonData) {
|
||||
const item = jsonData[name];
|
||||
if (item.pd < params.pdStart || params.pdEnd < item.pd) {
|
||||
continue;
|
||||
}
|
||||
if (item.recordingVolume.width < params.widthMin || params.widthMax < item.recordingVolume.width) {
|
||||
continue;
|
||||
}
|
||||
if (item.imagingDepth.from < params.depthMin || params.depthMax < item.imagingDepth.to) {
|
||||
continue;
|
||||
}
|
||||
if (params.cellType !== "any") {
|
||||
if (!item.cellType.includes(params.cellType as TCellType)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (params.area !== "all" && item.area !== params.area) {
|
||||
continue;
|
||||
}
|
||||
if (params.geneType !== "all" && item.geneType !== params.geneType) {
|
||||
continue;
|
||||
}
|
||||
if (params.keyword.length !== 0 && !item.remark.includes(params.keyword)) {
|
||||
continue;
|
||||
}
|
||||
results.push({ name, item });
|
||||
}
|
||||
return results;
|
||||
}
|
||||
export function exists(name: string): boolean {
|
||||
return Object.hasOwn(jsonData, name);
|
||||
}
|
||||
|
||||
export function search(params: SearchParams): SearchResults {
|
||||
const results: SearchResults = [];
|
||||
for (const name in jsonData) {
|
||||
const item = jsonData[name];
|
||||
if (item.pd < params.pdStart || params.pdEnd < item.pd) {
|
||||
continue;
|
||||
}
|
||||
if (item.recordingVolume.width < params.widthMin || params.widthMax < item.recordingVolume.width) {
|
||||
continue;
|
||||
}
|
||||
if (item.imagingDepth.from < params.depthMin || params.depthMax < item.imagingDepth.to) {
|
||||
continue;
|
||||
}
|
||||
if (params.cellType !== 'any') {
|
||||
if (!item.cellType.includes(params.cellType as TCellType)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (params.area !== 'all' && item.area !== params.area) {
|
||||
continue;
|
||||
}
|
||||
if (params.geneType !== 'all' && item.geneType !== params.geneType) {
|
||||
continue;
|
||||
}
|
||||
if (params.keyword.length !== 0 && !item.remark.includes(params.keyword)) {
|
||||
continue;
|
||||
}
|
||||
results.push({ name, item });
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const DataUtils = { exists, search };
|
||||
export default DataUtils;
|
||||
|
||||
+30
-25
@@ -1,16 +1,17 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Link, Route, Routes } from "react-router-dom";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import NotFound from "../not-found/NotFound";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./Help.module.css";
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import NotFound from '../not-found/NotFound';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './Help.module.css';
|
||||
|
||||
const HelpIndex = () => {
|
||||
const title = "Help";
|
||||
const title = 'Help';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<ul className={styles.index}>
|
||||
<li>
|
||||
@@ -49,11 +50,11 @@ const HelpSearchLinks = (
|
||||
);
|
||||
|
||||
const HelpSearch1 = () => {
|
||||
const title = "Help - Search dataset - Step 1";
|
||||
const title = 'Help - Search dataset - Step 1';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div>
|
||||
{HelpSearchLinks}
|
||||
@@ -67,11 +68,11 @@ const HelpSearch1 = () => {
|
||||
};
|
||||
|
||||
const HelpSearch2 = () => {
|
||||
const title = "Help - Search dataset - Step 2";
|
||||
const title = 'Help - Search dataset - Step 2';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div>
|
||||
{HelpSearchLinks}
|
||||
@@ -85,11 +86,11 @@ const HelpSearch2 = () => {
|
||||
};
|
||||
|
||||
const HelpSearch3 = () => {
|
||||
const title = "Help - Search dataset - Step 3";
|
||||
const title = 'Help - Search dataset - Step 3';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div>
|
||||
{HelpSearchLinks}
|
||||
@@ -103,11 +104,11 @@ const HelpSearch3 = () => {
|
||||
};
|
||||
|
||||
const HelpSearch4 = () => {
|
||||
const title = "Help - Search dataset - Step 4";
|
||||
const title = 'Help - Search dataset - Step 4';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div>
|
||||
{HelpSearchLinks}
|
||||
@@ -121,11 +122,11 @@ const HelpSearch4 = () => {
|
||||
};
|
||||
|
||||
const HelpSearch5 = () => {
|
||||
const title = "Help - Search dataset - Step 5";
|
||||
const title = 'Help - Search dataset - Step 5';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div>
|
||||
{HelpSearchLinks}
|
||||
@@ -133,7 +134,8 @@ const HelpSearch5 = () => {
|
||||
<p>
|
||||
The result page consists of details of the dataset.
|
||||
<br />
|
||||
Click the links in the "Download & View" section to download the dataset and/or view 3D model on your browser.{" "}
|
||||
Click the links in the "Download & View" section to download the dataset and/or view 3D model on your
|
||||
browser.{' '}
|
||||
</p>
|
||||
<div className={styles.figure}>
|
||||
<img src="/images/help-search5.jpg" alt={title} />
|
||||
@@ -143,11 +145,11 @@ const HelpSearch5 = () => {
|
||||
};
|
||||
|
||||
const HelpDataset = () => {
|
||||
const title = "Help - Downloadable dataset format";
|
||||
const title = 'Help - Downloadable dataset format';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<section>
|
||||
<div className={styles.title}>Dataset Format</div>
|
||||
@@ -155,21 +157,24 @@ const HelpDataset = () => {
|
||||
<img src="/images/help-dataset.jpg" alt={title} />
|
||||
</div>
|
||||
<p>
|
||||
<span className={styles.legend}>First row:</span> The first row of the data file indicates the size of the imaging volume. The first, second and third column represent the width, height and depth of the volume, respectively.
|
||||
<span className={styles.legend}>First row:</span> The first row of the data file indicates the size of the
|
||||
imaging volume. The first, second and third column represent the width, height and depth of the volume,
|
||||
respectively.
|
||||
</p>
|
||||
<p>
|
||||
<span className={styles.legend}>Other rows:</span> Each row consists of the type (first column) and the position (second to fourth columns for x, y and z position) of the cell.
|
||||
<span className={styles.legend}>Other rows:</span> Each row consists of the type (first column) and the position
|
||||
(second to fourth columns for x, y and z position) of the cell.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const HelpViewer = () => {
|
||||
const title = "Help - 3D Viewer Manual";
|
||||
const title = 'Help - 3D Viewer Manual';
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle(title));
|
||||
}, [dispatch, title]);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<section>
|
||||
<div className={styles.title}>3D Viewer Manual</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./Loading.module.css";
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './Loading.module.css';
|
||||
|
||||
const Loading: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle("Loading now..."));
|
||||
dispatch(setTitle('Loading now...'));
|
||||
}, [dispatch]);
|
||||
|
||||
return <div className={styles.loading}>Loading now...</div>;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import JsonNews_ from "../../assets/news.json";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./News.module.css";
|
||||
const nl2br = require("react-nl2br");
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import JsonNews_ from '../../assets/news.json';
|
||||
import nl2br from '../../utils/nl2br';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './News.module.css';
|
||||
|
||||
interface IJsonNewsItem {
|
||||
date: string;
|
||||
@@ -24,6 +25,7 @@ const News: React.FC = () => {
|
||||
<div className={styles.news}>
|
||||
{JsonNews.map((item, key) => {
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: static JSON data
|
||||
<div className={styles.item} key={key}>
|
||||
<div className={styles.date}>{item.date}</div>
|
||||
<div className={styles.desc}>{nl2br(item.desc)}</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./NotFound.module.css";
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './NotFound.module.css';
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle("Page Not Found"));
|
||||
dispatch(setTitle('Page Not Found'));
|
||||
}, [dispatch]);
|
||||
|
||||
return <div className={styles.notFound}>Page not found.</div>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import styles from "./PageTitle.module.css";
|
||||
import { selectPageTitle } from "./pageTitleSlice";
|
||||
import type React from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useAppSelector } from '../../app/hooks';
|
||||
import styles from './PageTitle.module.css';
|
||||
import { selectPageTitle } from './pageTitleSlice';
|
||||
|
||||
const PageTitle: React.FC = () => {
|
||||
const title = useAppSelector(selectPageTitle);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { RootState } from "../../app/store";
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { RootState } from '../../app/store';
|
||||
|
||||
export interface PageTitleState {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const initialState: PageTitleState = {
|
||||
value: "",
|
||||
value: '',
|
||||
};
|
||||
|
||||
const pageTitleSlice = createSlice({
|
||||
name: "pageTitle",
|
||||
name: 'pageTitle',
|
||||
initialState,
|
||||
reducers: {
|
||||
setTitle: (state, action: PayloadAction<string>) => {
|
||||
|
||||
@@ -1,198 +1,203 @@
|
||||
import React, { Component } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import DataUtils, { SearchResults } from "../data/DataUtils";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import { setResults } from "../search-results/searchResultsSlice";
|
||||
import styles from "./SearchForm.module.css";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAppDispatch } from '../../app/hooks';
|
||||
import DataUtils, { type SearchResults } from '../data/DataUtils';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import { setResults } from '../search-results/searchResultsSlice';
|
||||
import styles from './SearchForm.module.css';
|
||||
|
||||
interface FCProps {}
|
||||
|
||||
interface Props extends FCProps {
|
||||
navigate: any;
|
||||
dispatch: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
pdStart: string;
|
||||
pdEnd: string;
|
||||
widthMin: string;
|
||||
widthMax: string;
|
||||
depthMin: string;
|
||||
depthMax: string;
|
||||
cellType: string;
|
||||
area: string;
|
||||
geneType: string;
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
class SearchForm extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
pdStart: "44",
|
||||
pdEnd: "100",
|
||||
widthMin: "317",
|
||||
widthMax: "512",
|
||||
depthMin: "90",
|
||||
depthMax: "230",
|
||||
cellType: "any",
|
||||
area: "all",
|
||||
geneType: "all",
|
||||
keyword: "",
|
||||
};
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleSelectChange = this.handleSelectChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(setTitle("Database Search"));
|
||||
}
|
||||
|
||||
doSearch(): SearchResults {
|
||||
const toNumber = (v: string): number => parseInt(v.trim(), 10);
|
||||
const params = {
|
||||
pdStart: toNumber(this.state.pdStart),
|
||||
pdEnd: toNumber(this.state.pdEnd),
|
||||
widthMin: toNumber(this.state.widthMin),
|
||||
widthMax: toNumber(this.state.widthMax),
|
||||
depthMin: toNumber(this.state.depthMin),
|
||||
depthMax: toNumber(this.state.depthMax),
|
||||
cellType: this.state.cellType.trim(),
|
||||
area: this.state.area.trim(),
|
||||
geneType: this.state.geneType.trim(),
|
||||
keyword: this.state.keyword.trim(),
|
||||
};
|
||||
return DataUtils.search(params);
|
||||
}
|
||||
|
||||
handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const target = event.target;
|
||||
const value = target.value;
|
||||
const name = target.name;
|
||||
switch (name) {
|
||||
case "pdStart":
|
||||
this.setState({ pdStart: value });
|
||||
break;
|
||||
case "pdEnd":
|
||||
this.setState({ pdStart: value });
|
||||
break;
|
||||
case "widthMin":
|
||||
this.setState({ widthMin: value });
|
||||
break;
|
||||
case "widthMax":
|
||||
this.setState({ widthMax: value });
|
||||
break;
|
||||
case "depthMin":
|
||||
this.setState({ depthMin: value });
|
||||
break;
|
||||
case "depthMax":
|
||||
this.setState({ depthMax: value });
|
||||
break;
|
||||
case "cellType":
|
||||
this.setState({ cellType: value });
|
||||
break;
|
||||
case "keyword":
|
||||
this.setState({ keyword: value });
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleSelectChange: React.ChangeEventHandler<HTMLSelectElement> = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const target = event.target;
|
||||
const value = target.value;
|
||||
const name = target.name;
|
||||
switch (name) {
|
||||
case "area":
|
||||
this.setState({ area: value });
|
||||
break;
|
||||
case "geneType":
|
||||
this.setState({ geneType: value });
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit: React.FormEventHandler<HTMLFormElement> = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
const results = this.doSearch();
|
||||
this.props.dispatch(setResults(results));
|
||||
this.props.navigate("/search/results");
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="pdStart">Postnatal Days</label>
|
||||
<div className={styles.value}>
|
||||
<input type="number" name="pdStart" className={styles.txtParam} value={this.state.pdStart} onChange={this.handleInputChange} />
|
||||
<span className={styles.range}>-</span>
|
||||
<input type="number" name="pdEnd" className={styles.txtParam} value={this.state.pdEnd} onChange={this.handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="widthMin">Recording volume width (height)</label>
|
||||
<div className={styles.value}>
|
||||
<input type="number" name="widthMin" className={styles.txtParam} value={this.state.widthMin} onChange={this.handleInputChange} />
|
||||
<span className={styles.range}>-</span>
|
||||
<input type="number" name="widthMax" className={styles.txtParam} value={this.state.widthMax} onChange={this.handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="depthMin">Recording volume depth</label>
|
||||
<div className={styles.value}>
|
||||
<input type="number" name="depthMin" className={styles.txtParam} value={this.state.depthMin} onChange={this.handleInputChange} />
|
||||
<span className={styles.range}>-</span>
|
||||
<input type="number" name="depthMax" className={styles.txtParam} value={this.state.depthMax} onChange={this.handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="cellType">Cell type (E, I, G, P, S, or "any")</label>
|
||||
<div className={styles.value}>
|
||||
<input type="text" name="cellType" className={styles.txtParamStr} value={this.state.cellType} onChange={this.handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="area">Target area</label>
|
||||
<div className={styles.value}>
|
||||
<select name="area" className={styles.selParam} value={this.state.area} onChange={this.handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="L2/3, V1">L2/3 in the visual cortex</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="geneType">Mouse line</label>
|
||||
<div className={styles.value}>
|
||||
<select name="geneType" className={styles.selParam} value={this.state.geneType} onChange={this.handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="VGAT-Venus">VGAT-Venus</option>
|
||||
<option value="PV/myrGFP-LDLRct">PV/myrGFP-LDLRct</option>
|
||||
<option value="Dlx5/6-GCaMP3">Dlx5/6-GCaMP3</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="keyword">Keyword Search</label>
|
||||
<div className={styles.value}>
|
||||
<input type="text" name="keyword" className={styles.txtKeyword} value={this.state.keyword} onChange={this.handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<div className={styles.submit}>
|
||||
<input type="submit" value="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SearchFormFC: React.FC<FCProps> = (props: FCProps) => {
|
||||
const SearchForm: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
return <SearchForm navigate={navigate} dispatch={dispatch} />;
|
||||
|
||||
const [pdStart, setPdStart] = useState('44');
|
||||
const [pdEnd, setPdEnd] = useState('100');
|
||||
const [widthMin, setWidthMin] = useState('317');
|
||||
const [widthMax, setWidthMax] = useState('512');
|
||||
const [depthMin, setDepthMin] = useState('90');
|
||||
const [depthMax, setDepthMax] = useState('230');
|
||||
const [cellType, setCellType] = useState('any');
|
||||
const [area, setArea] = useState('all');
|
||||
const [geneType, setGeneType] = useState('all');
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setTitle('Database Search'));
|
||||
}, [dispatch]);
|
||||
|
||||
const doSearch = (): SearchResults => {
|
||||
const toNumber = (v: string): number => parseInt(v.trim(), 10);
|
||||
const params = {
|
||||
pdStart: toNumber(pdStart),
|
||||
pdEnd: toNumber(pdEnd),
|
||||
widthMin: toNumber(widthMin),
|
||||
widthMax: toNumber(widthMax),
|
||||
depthMin: toNumber(depthMin),
|
||||
depthMax: toNumber(depthMax),
|
||||
cellType: cellType.trim(),
|
||||
area: area.trim(),
|
||||
geneType: geneType.trim(),
|
||||
keyword: keyword.trim(),
|
||||
};
|
||||
return DataUtils.search(params);
|
||||
};
|
||||
|
||||
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const { name, value } = event.target;
|
||||
switch (name) {
|
||||
case 'pdStart':
|
||||
setPdStart(value);
|
||||
break;
|
||||
case 'pdEnd':
|
||||
setPdEnd(value);
|
||||
break;
|
||||
case 'widthMin':
|
||||
setWidthMin(value);
|
||||
break;
|
||||
case 'widthMax':
|
||||
setWidthMax(value);
|
||||
break;
|
||||
case 'depthMin':
|
||||
setDepthMin(value);
|
||||
break;
|
||||
case 'depthMax':
|
||||
setDepthMax(value);
|
||||
break;
|
||||
case 'cellType':
|
||||
setCellType(value);
|
||||
break;
|
||||
case 'keyword':
|
||||
setKeyword(value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectChange: React.ChangeEventHandler<HTMLSelectElement> = (event) => {
|
||||
const { name, value } = event.target;
|
||||
switch (name) {
|
||||
case 'area':
|
||||
setArea(value);
|
||||
break;
|
||||
case 'geneType':
|
||||
setGeneType(value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault();
|
||||
const results = doSearch();
|
||||
dispatch(setResults(results));
|
||||
navigate('/search/results');
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="pdStart">Postnatal Days</label>
|
||||
<div className={styles.value}>
|
||||
<input
|
||||
type="number"
|
||||
name="pdStart"
|
||||
className={styles.txtParam}
|
||||
value={pdStart}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<span className={styles.range}>-</span>
|
||||
<input type="number" name="pdEnd" className={styles.txtParam} value={pdEnd} onChange={handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="widthMin">Recording volume width (height)</label>
|
||||
<div className={styles.value}>
|
||||
<input
|
||||
type="number"
|
||||
name="widthMin"
|
||||
className={styles.txtParam}
|
||||
value={widthMin}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<span className={styles.range}>-</span>
|
||||
<input
|
||||
type="number"
|
||||
name="widthMax"
|
||||
className={styles.txtParam}
|
||||
value={widthMax}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="depthMin">Recording volume depth</label>
|
||||
<div className={styles.value}>
|
||||
<input
|
||||
type="number"
|
||||
name="depthMin"
|
||||
className={styles.txtParam}
|
||||
value={depthMin}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<span className={styles.range}>-</span>
|
||||
<input
|
||||
type="number"
|
||||
name="depthMax"
|
||||
className={styles.txtParam}
|
||||
value={depthMax}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="cellType">Cell type (E, I, G, P, S, or "any")</label>
|
||||
<div className={styles.value}>
|
||||
<input
|
||||
type="text"
|
||||
name="cellType"
|
||||
className={styles.txtParamStr}
|
||||
value={cellType}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="area">Target area</label>
|
||||
<div className={styles.value}>
|
||||
<select name="area" className={styles.selParam} value={area} onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="L2/3, V1">L2/3 in the visual cortex</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="geneType">Mouse line</label>
|
||||
<div className={styles.value}>
|
||||
<select name="geneType" className={styles.selParam} value={geneType} onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="VGAT-Venus">VGAT-Venus</option>
|
||||
<option value="PV/myrGFP-LDLRct">PV/myrGFP-LDLRct</option>
|
||||
<option value="Dlx5/6-GCaMP3">Dlx5/6-GCaMP3</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="keyword">Keyword Search</label>
|
||||
<div className={styles.value}>
|
||||
<input
|
||||
type="text"
|
||||
name="keyword"
|
||||
className={styles.txtKeyword}
|
||||
value={keyword}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<div className={styles.submit}>
|
||||
<input type="submit" value="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchFormFC;
|
||||
export default SearchForm;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { setTitle } from "../page-title/pageTitleSlice";
|
||||
import styles from "./SearchResults.module.css";
|
||||
import { selectSearchResults } from "./searchResultsSlice";
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAppDispatch, useAppSelector } from '../../app/hooks';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './SearchResults.module.css';
|
||||
import { selectSearchResults } from './searchResultsSlice';
|
||||
|
||||
const SearchResults: React.FC = () => {
|
||||
const results = useAppSelector(selectSearchResults);
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setTitle("Search Results"));
|
||||
dispatch(setTitle('Search Results'));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
@@ -19,7 +20,7 @@ const SearchResults: React.FC = () => {
|
||||
) : (
|
||||
results.map((result, key) => {
|
||||
return (
|
||||
<div className={styles.result} key={key}>
|
||||
<div className={styles.result} key={result.name}>
|
||||
<div className={styles.title}>
|
||||
<Link to={`/data/${result.name}`}>
|
||||
{key + 1}. {result.name}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { RootState } from "../../app/store";
|
||||
import { SearchResults } from "../data/DataUtils";
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { RootState } from '../../app/store';
|
||||
import type { SearchResults } from '../data/DataUtils';
|
||||
|
||||
export interface SearchResultsState {
|
||||
value: SearchResults;
|
||||
@@ -11,7 +11,7 @@ const initialState: SearchResultsState = {
|
||||
};
|
||||
|
||||
const searchResultsSlice = createSlice({
|
||||
name: "searchResults",
|
||||
name: 'searchResults',
|
||||
initialState,
|
||||
reducers: {
|
||||
setResults: (state, action: PayloadAction<SearchResults>) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import NotFound from "../not-found/NotFound";
|
||||
import SearchForm from "../search-form/SearchForm";
|
||||
import SearchResults from "../search-results/SearchResults";
|
||||
import styles from "./Search.module.css";
|
||||
import type React from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import NotFound from '../not-found/NotFound';
|
||||
import SearchForm from '../search-form/SearchForm';
|
||||
import SearchResults from '../search-results/SearchResults';
|
||||
import styles from './Search.module.css';
|
||||
|
||||
const Search: React.FC = () => {
|
||||
return (
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
@import-normalize;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
+9
-17
@@ -1,15 +1,12 @@
|
||||
import React from "react";
|
||||
import "react-app-polyfill/ie11";
|
||||
import "react-app-polyfill/stable";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import { Provider } from "react-redux";
|
||||
import App from "./App";
|
||||
import { store } from "./app/store";
|
||||
import "./index.css";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { Provider } from 'react-redux';
|
||||
import App from './App';
|
||||
import { store } from './app/store';
|
||||
import './index.css';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
@@ -17,10 +14,5 @@ root.render(
|
||||
<App />
|
||||
</HelmetProvider>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
@@ -1,5 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
const newlineRegex = /(\r\n|\r|\n)/g;
|
||||
|
||||
export default function nl2br(str: string): (string | React.ReactElement)[] {
|
||||
if (typeof str !== 'string') {
|
||||
return [str];
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
return str.split(newlineRegex).map((line) => {
|
||||
if (line.match(newlineRegex)) {
|
||||
return <br key={`br-${counter++}`} />;
|
||||
}
|
||||
return <React.Fragment key={`line-${counter++}`}>{line}</React.Fragment>;
|
||||
});
|
||||
}
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
// Workaround: @types/three's exports map uses .js extensions that bundler resolution
|
||||
// cannot resolve. Declare TrackballControls directly.
|
||||
declare module 'three/examples/jsm/controls/TrackballControls' {
|
||||
import type { Camera, Vector3 } from 'three';
|
||||
export class TrackballControls {
|
||||
constructor(camera: Camera, domElement?: HTMLElement);
|
||||
object: Camera;
|
||||
domElement: HTMLElement;
|
||||
enabled: boolean;
|
||||
screen: { left: number; top: number; right: number; bottom: number };
|
||||
rotateSpeed: number;
|
||||
zoomSpeed: number;
|
||||
panSpeed: number;
|
||||
staticMoving: boolean;
|
||||
dynamicDampingFactor: number;
|
||||
minDistance: number;
|
||||
maxDistance: number;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
noRotate: boolean;
|
||||
noZoom: boolean;
|
||||
noPan: boolean;
|
||||
noRoll: boolean;
|
||||
target: Vector3;
|
||||
update(deltaTime?: number): boolean;
|
||||
reset(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023", "DOM"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client", "node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Project-specific settings */
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
|
||||
/* Linting */
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
+2
-24
@@ -1,26 +1,4 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
target: 'es2023',
|
||||
chunkSizeWarningLimit: 1000,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('react') || id.includes('react-dom')) {
|
||||
return 'vendor';
|
||||
}
|
||||
if (id.includes('three')) {
|
||||
return 'three';
|
||||
}
|
||||
return 'vendor-lib';
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user