Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3f684a90b4
|
|||
|
d73f4ef2b9
|
|||
|
e7b065f585
|
|||
|
ff3e5b8ebb
|
|||
|
d12bf5c9e2
|
|||
|
5947ef1144
|
|||
|
a56a95769f
|
|||
|
c22bf6ee0c
|
|||
|
aae43e9421
|
@@ -0,0 +1,50 @@
|
||||
---
|
||||
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: 332b6fc533ae9085eafbba6827ff7798c85e4b38
|
||||
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: Message Validation
|
||||
1. After drafting the message in Step 2, validate it against **every rule** listed in `references/conventional_commits.md` before proceeding. Check the following:
|
||||
* **Type:** The commit type must be one of the allowed types (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert).
|
||||
* **Language:** The entire message must be in English (unless the user explicitly requested another language).
|
||||
* **Case & Punctuation:** The description must be lowercase and must NOT end with a period.
|
||||
* **Imperative Mood:** The description must use imperative mood (e.g., "add", not "added" or "adds").
|
||||
* **Line Length:** The subject line must not exceed 72 characters. **Every line in the body must also be wrapped at 72 characters or less.** If any line exceeds this limit, insert line breaks to fix it.
|
||||
* **`-m` Flags:** The command must use at most three `-m` flags (one per structural part). Body lines must NOT be split across multiple `-m` flags.
|
||||
* **Literal Newlines:** Multi-line body/footer content must use **literal newlines** inside double-quoted strings, NOT `\n` escape sequences (which shells commit as literal backslash-n).
|
||||
* **Co-authored-by:** No `Co-authored-by:` trailers unless explicitly requested.
|
||||
2. If **any** rule is violated, fix the draft and re-validate from step 1. Repeat this loop until all checks pass. Only proceed to Step 4 once the message passes every validation check.
|
||||
|
||||
### Step 4: 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,77 @@
|
||||
# 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").
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
When validating a draft commit message, check **every** item below. If any check fails, fix the issue and re-validate until all pass.
|
||||
|
||||
| # | Check | Rule |
|
||||
|---|-------|------|
|
||||
| 1 | **Type valid** | The type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert. |
|
||||
| 2 | **Language** | The message must be in English (unless the user explicitly requested another language). |
|
||||
| 3 | **Lowercase** | The description must be lowercase. |
|
||||
| 4 | **No period** | The description must NOT end with a period. |
|
||||
| 5 | **Imperative mood** | Use imperative mood (e.g., "add" not "added", "adds"). |
|
||||
| 6 | **Subject ≤ 72 chars** | The subject line must not exceed 72 characters. |
|
||||
| 7 | **Body lines ≤ 72 chars** | **Every line in the body must be wrapped at 72 characters or less.** |
|
||||
| 8 | **At most 3 `-m` flags** | Use at most one `-m` per structural part; do NOT split body lines across multiple `-m` flags. |
|
||||
| 9 | **Literal newlines** | Multi-line body/footer must use literal newlines inside double quotes, NOT `\n` escape sequences. |
|
||||
| 10 | **No Co-authored-by** | Do not include `Co-authored-by:` unless explicitly requested. |
|
||||
+25
-13
@@ -1,24 +1,36 @@
|
||||
# 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
|
||||
.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
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"preLaunchTask": "npm: dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
// Extensions - Biome
|
||||
// - see: biome.json
|
||||
// Exteions - cSpell
|
||||
// - see: .cspell.json
|
||||
// Extensions - ESLint
|
||||
"eslint.enable": false,
|
||||
// Extensions - HTML
|
||||
"html.format.wrapLineLength": 0,
|
||||
// Extentions - Typescript
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.updateImportsOnFileMove.enabled": "always"
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "custom",
|
||||
"pattern": { "regexp": "^$" },
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": ".*",
|
||||
"endsPattern": "Local:"
|
||||
}
|
||||
},
|
||||
"label": "npm: dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,45 +1,45 @@
|
||||
# CelLoc-3D
|
||||
|
||||
React project for celloc3d.brain.riken.jp
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
### `npm run dev`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br>
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br>
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br>
|
||||
Builds the app for production to the `dist` folder.<br>
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
### `npm run preview`
|
||||
|
||||
### `npm run eject`
|
||||
Locally preview the production build.
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
### `npm run lint`
|
||||
|
||||
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.
|
||||
Run ESLint to check for code quality issues.
|
||||
|
||||
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.
|
||||
### `npm run format`
|
||||
|
||||
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.
|
||||
Format code using Biome.
|
||||
|
||||
### `npm run format:check`
|
||||
|
||||
Check code formatting without making changes.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
You can learn more in the [Vite documentation](https://vite.dev/guide/).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
To learn React, check out the [React documentation](https://react.dev/).
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["**", "!**/dist", "!**/node_modules"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 120
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"domains": {
|
||||
"react": "recommended"
|
||||
},
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"language": "en,en-GB",
|
||||
"words": [
|
||||
"Aergic",
|
||||
"astrocytes",
|
||||
"celloc",
|
||||
"Ebina",
|
||||
"Flpe",
|
||||
"geometory",
|
||||
"glutamatergic",
|
||||
"Hioki",
|
||||
"Hirosawa",
|
||||
"Imayoshi",
|
||||
"Kameda",
|
||||
"Kaneko",
|
||||
"Kimura",
|
||||
"NOWEBGL",
|
||||
"pogodin",
|
||||
"RIKEN",
|
||||
"Saitama",
|
||||
"SDCN",
|
||||
"Sohya",
|
||||
"Tadaharu",
|
||||
"Teppei",
|
||||
"Tsumoto",
|
||||
"VGAT",
|
||||
"Wako",
|
||||
"webgl",
|
||||
"Yanagawa"
|
||||
],
|
||||
"ignorePaths": ["dist", "node_modules", "package-lock.json"]
|
||||
}
|
||||
@@ -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."
|
||||
content="CelLoc-3D is a database of the 3D arrangement of neocortical cells (glutamatergic/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
+3241
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.1",
|
||||
"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": {
|
||||
"@dr.pogodin/react-helmet": "^3.2.2",
|
||||
"@reduxjs/toolkit": "^2.12.0",
|
||||
"ky": "^2.0.2",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-ga4": "^3.0.1",
|
||||
"react-redux": "^9.3.0",
|
||||
"react-router-dom": "^7.17.0",
|
||||
"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.16",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/node": "^24.13.1",
|
||||
"@types/react": "^19.2.17",
|
||||
"@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.1",
|
||||
"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.61.0",
|
||||
"vite": "^8.0.16"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0001.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0001"
|
||||
"caption": "3D Arrangements of cells in TE0001"
|
||||
},
|
||||
"download": "\/data\/TE0001.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0002.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0002"
|
||||
"caption": "3D Arrangements of cells in TE0002"
|
||||
},
|
||||
"download": "\/data\/TE0002.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0003.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0003"
|
||||
"caption": "3D Arrangements of cells in TE0003"
|
||||
},
|
||||
"download": "\/data\/TE0003.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0004.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0004"
|
||||
"caption": "3D Arrangements of cells in TE0004"
|
||||
},
|
||||
"download": "\/data\/TE0004.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0005.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0005"
|
||||
"caption": "3D Arrangements of cells in TE0005"
|
||||
},
|
||||
"download": "\/data\/TE0005.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,18 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0006.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0006"
|
||||
"caption": "3D Arrangements of cells in TE0006"
|
||||
},
|
||||
"download": "\/data\/TE0006.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,18 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0007.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0007"
|
||||
"caption": "3D Arrangements of cells in TE0007"
|
||||
},
|
||||
"download": "\/data\/TE0007.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,18 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0008.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0008"
|
||||
"caption": "3D Arrangements of cells in TE0008"
|
||||
},
|
||||
"download": "\/data\/TE0008.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0009.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0009"
|
||||
"caption": "3D Arrangements of cells in TE0009"
|
||||
},
|
||||
"download": "\/data\/TE0009.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0010.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0010"
|
||||
"caption": "3D Arrangements of cells in TE0010"
|
||||
},
|
||||
"download": "\/data\/TE0010.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0011.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0011"
|
||||
"caption": "3D Arrangements of cells in TE0011"
|
||||
},
|
||||
"download": "\/data\/TE0011.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0012.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0012"
|
||||
"caption": "3D Arrangements of cells in TE0012"
|
||||
},
|
||||
"download": "\/data\/TE0012.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0013.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0013"
|
||||
"caption": "3D Arrangements of cells in TE0013"
|
||||
},
|
||||
"download": "\/data\/TE0013.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0014.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0014"
|
||||
"caption": "3D Arrangements of cells in TE0014"
|
||||
},
|
||||
"download": "\/data\/TE0014.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0015.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0015"
|
||||
"caption": "3D Arrangements of cells in TE0015"
|
||||
},
|
||||
"download": "\/data\/TE0015.dat",
|
||||
"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",
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
"author": "Ebina, T., Sohya, K., Imayoshi, I., Yin, S.T., Kimura, R., Yanagawa, Y., Kameda, H., Hioki, H., Kaneko, T., and Tsumoto, T.",
|
||||
"figure": {
|
||||
"file": "\/data\/TE0016.png",
|
||||
"caption": "3D Arrangemtns of cells in TE0016"
|
||||
"caption": "3D Arrangements of cells in TE0016"
|
||||
},
|
||||
"download": "\/data\/TE0016.dat",
|
||||
"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({ hitType: 'pageview', page: location.pathname });
|
||||
}, [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 />;
|
||||
};
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
.contact p {
|
||||
margin: 5px 0 15px 15px;
|
||||
}
|
||||
.notice {
|
||||
margin: 10px 0 20px;
|
||||
color: #ff0000;
|
||||
}
|
||||
.note {
|
||||
margin-left: 15px;
|
||||
font-style: italic;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
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 Notice from '../notice/Notice';
|
||||
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>
|
||||
<Notice />
|
||||
<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,107 @@ 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 });
|
||||
|
||||
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;
|
||||
|
||||
+31
-26
@@ -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>
|
||||
@@ -20,7 +21,7 @@ const HelpIndex = () => {
|
||||
<Link to="/help/dataset">Downloadable dataset format</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/help/viewer">3D Viewer Mannual</Link>
|
||||
<Link to="/help/viewer">3D Viewer Manual</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
@@ -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,8 +1,8 @@
|
||||
.news {
|
||||
margin: 10px;
|
||||
margin: 20px;
|
||||
}
|
||||
.item {
|
||||
margin: 0 10px 10px 10px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
.item:after {
|
||||
content: ".";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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 Notice from '../notice/Notice';
|
||||
import { setTitle } from '../page-title/pageTitleSlice';
|
||||
import styles from './News.module.css';
|
||||
|
||||
interface IJsonNewsItem {
|
||||
date: string;
|
||||
@@ -22,8 +24,10 @@ const News: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.news}>
|
||||
<Notice />
|
||||
{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>;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.notice {
|
||||
margin: 10px 0 20px;
|
||||
color: #ff0000;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type React from 'react';
|
||||
import styles from './Notice.module.css';
|
||||
|
||||
const Notice: React.FC = () => {
|
||||
return (
|
||||
<section className={styles.notice}>This site has been archived since May 2019 and is no longer updated.</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notice;
|
||||
@@ -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 { Helmet } from '@dr.pogodin/react-helmet';
|
||||
import type React from 'react';
|
||||
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 { HelmetProvider } from '@dr.pogodin/react-helmet';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
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