build: migrate from Create React App to Vite

- Replace CRA with Vite as the build tool
- Rename index.tsx to main.tsx (Vite convention)
- Add vite.config.ts, tsconfig.app.json, tsconfig.node.json
- Move index.html from public/ to project root
- Replace yarn.lock with package-lock.json
- Remove CRA-specific files (reportWebVitals,
  setupTests, react-app-env.d.ts)
- Update package.json, tsconfig.json, .gitignore, and README.md
- Adjust all source files for Vite compatibility
This commit is contained in:
2026-05-25 00:16:32 +09:00
parent 6f32b7fce2
commit 4e4a0c5de1
208 changed files with 18557 additions and 20365 deletions
+38
View File
@@ -0,0 +1,38 @@
---
description: Analyze repository changes, gather contextual intent from the current codebase status, and generate structurally compliant Git commit messages.
metadata:
github-path: skills/git-commit
github-ref: refs/heads/main
github-repo: https://github.com/orrisroot/agent-skills
github-tree-sha: 661413ef1afaece6071d57b938ef050d4862e4d4
name: git-commit
---
# Git Commit Operator
## 1. Purpose
Analyze repository changes, gather contextual intent from the current codebase status, and generate structurally compliant Git commit messages.
## 2. Goals
* **Atomic Evaluation:** Review diffs to ensure changes represent a single, focused utility. Suggest breaking up large, mixed changes into distinct, atomic commits.
* **Contextual Analysis:** Cross-reference physical changes with the recent discussion history to understand not just *what* changed, but *why* it changed.
* **Executable Output:** Provide ready-to-run terminal commands that apply the correct formatting flags, paragraph separators, and line-wrapping behaviors.
## 3. Operational Workflow for Commits
You must execute the following sequential workflow whenever a commit task is initiated or modifications are targeted for staging:
### Step 1: Diff and Intent Inspection
1. Run `git diff --cached` to evaluate the staged modifications.
2. Cross-reference the structural changes against recent code review comments, commit messages, or the active chat context to confirm the primary engineering objective.
3. If those sources do not clearly explain the change, ask one concise clarifying question before generating the draft.
### Step 2: Message Draft Formulation
1. Construct the message draft strictly using the type classifications and syntax limits specified in `references/conventional_commits.md`.
2. Present the drafted structure to the user for validation, highlighting the assigned commit type and scope.
### Step 3: Terminal Command Delivery
1. Upon user confirmation, output the explicit, single-line terminal command using at most three `-m` flags as specified in the formatting policy.
* **CRITICAL:** Use at most one `-m` flag per structural part (Subject, Body, and Footer).
* **NEVER** use multiple `-m` flags for individual lines or bullet points within the body. Doing so causes Git to insert unwanted blank lines.
* **CRITICAL:** For multi-line body or footer content, you must use a single double-quoted string containing **literal newlines (actual line breaks)** within the command. Do NOT use escape sequences like `\n` (which shell command execution will commit literally) or additional `-m` flags.
Process each step in order and complete the current step before moving to the next.
@@ -0,0 +1,60 @@
# Reference: Conventional Commits Guidelines
When drafting, validating, or executing Git commits, you must strictly adhere to the specification and specific formatting rules defined below.
## Message Format
```plain
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
```
## Allowed Types
* **feat**: A new feature for the user.
* **fix**: A bug fix for the user.
* **docs**: Documentation only changes.
* **style**: Changes that do not affect the meaning of the code (formatting, missing semi-colons, etc).
* **refactor**: A code change that neither fixes a bug nor adds a feature.
* **perf**: A code change that improves performance.
* **test**: Adding missing tests or correcting existing tests.
* **build**: Changes that affect the build system or external dependencies.
* **ci**: Changes to CI configuration files and scripts.
* **chore**: Other changes that do not modify src or test files.
* **revert**: Reverts a previous commit.
## Strict Constraints
1. **Default Message Language**:
* **The commit message must be written exclusively in English** unless the user provides explicit, specific instructions to use another language for that particular commit.
2. **Line Length Limits**:
* The subject line (first line) must not exceed 72 characters, ideally 50 characters or less.
* **Every single line within the commit message body must also be wrapped at 72 characters or less.** If an explanation runs longer, you must manually insert line breaks to keep each line under the 72-character threshold.
3. **Part Separation via Multiple `-m` Flags (At Most Three)**:
* Use at most one `-m` flag per structural part: the subject line, the body, and the footer. Use at most three `-m` flags in total.
* **NEVER split body lines or bullet lists across multiple `-m` flags.** Doing so inserts unwanted blank lines because Git automatically treats each `-m` flag as a separate paragraph.
* **DO NOT use escape sequences like `\n` in double quotes.** Shells like bash will treat `\n` as literal backslash-n characters and commit them as-is.
* **Use literal newlines (actual line breaks)** inside double quotes to write multi-line bodies or footers.
* *Example:*
```bash
git commit -m "feat(auth): add jwt authentication" -m "Validate tokens on every incoming request.
Secure endpoints by rejecting expired credentials.
- Added middleware for token validation
- Removed legacy session handling" -m "BREAKING CHANGE: The old session-based cookie auth is deprecated."
```
4. **Co-authored-by Restriction**:
* **Do not include any `Co-authored-by:` trailers** in the commit message or footer unless the user explicitly requests you to add credit for a co-author.
5. **Case and Punctuation**:
* The description line must be written in lowercase and must not end with a period.
6. **Imperative Mood**:
* Always use the imperative mood in the description (e.g., use "add", "fix", "change" instead of "added", "fixes", "changed").
+38 -22
View File
@@ -1,28 +1,44 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.eslintcache
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# database contents
# Dependencies
node_modules
/.pnp
.pnp.js
# Production builds
build
dist
dist-ssr
# Testing
coverage
# Environment variables
.env.local
.env.development.local
.env.test.local
.env.production.local
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Misc
.eslintcache
# Custom - database contents
/public/modules
#/src/xoonips/assets/*.json
+44 -30
View File
@@ -1,45 +1,59 @@
# NeuroImaging Platform
React project for nimg.neuroinf.jp
## Tech Stack
- **Build tool**: [Vite](https://vitejs.dev/)
- **Framework**: [React 19](https://react.dev/)
- **Language**: [TypeScript](https://www.typescriptlang.org/)
- **HTTP client**: [ky](https://github.com/sindresorhus/ky)
- **Formatter**: [Biome](https://biomejs.dev/)
## Prerequisites
- Node.js 24
## Getting Started
```bash
npm install
npm run dev
```
Open [http://localhost:5173](http://localhost:5173) to view the app in the browser.
## Available Scripts
In the project directory, you can run:
### `npm run dev`
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) 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.
Starts the development server with hot module replacement.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
Builds the app for production to the `build` folder.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
### `npm run preview`
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
Previews the production build locally.
### `npm run eject`
### `npm run format`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
Formats all source files using Biome.
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.
## Project Structure
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.
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.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
```
src/
├── brainexplorer/ # Brain Explorer module
├── common/ # Shared components and utilities
├── config.ts # Application configuration
├── credits/ # Credits module
├── custom/ # Custom components (Header, Footer, Page)
├── functions.ts # Utility functions
├── index.tsx # Entry point
├── nimgcenter/ # NIMG Center module
├── nimgdocs/ # NIMG Docs module
├── nimgsearch/ # NIMG Search module
└── xoonips/ # XooNIps integration module
```
+28
View File
@@ -0,0 +1,28 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"includes": ["**", "!**/build", "!**/node_modules"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 120
},
"linter": {
"enabled": false
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"javascript": {
"formatter": {
"quoteStyle": "single",
"trailingCommas": "all"
}
}
}
+42
View File
@@ -0,0 +1,42 @@
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(['build']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'react-hooks/set-state-in-effect': 'warn',
'react-hooks/exhaustive-deps': 'warn',
'react-hooks/refs': 'warn',
'react-refresh/only-export-components': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
'no-irregular-whitespace': ['warn', { skipStrings: true, skipComments: true, skipTemplates: true }],
'no-useless-assignment': 'warn',
},
},
{
files: ['src/nimgsearch/Nimgsearch.tsx', 'src/xoonips/item-type/lib/AdvancedSearchBase.tsx'],
rules: {
'react-hooks/refs': 'off',
},
},
]);
+6 -5
View File
@@ -1,19 +1,20 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="NIMG-PF is the Neuroinformatics site of Neuroimaging under NIJC."
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" href="/manifest.json" />
<title>NIMG - NeuroImaging Platform</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+3594
View File
File diff suppressed because it is too large Load Diff
+40 -51
View File
@@ -2,63 +2,52 @@
"name": "nimg",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"format": "biome check --write .",
"format:check": "biome check .",
"preview": "vite preview"
},
"dependencies": {
"@orrisroot/react-html-parser": "^2.1.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"@types/async-lock": "^1.1.5",
"@types/jest": "^28.1.2",
"@types/lokijs": "^1.5.7",
"@types/node": "^18.0.0",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.5",
"@orrisroot/react-html-parser": "^3.0.0",
"@types/async-lock": "^1.4.2",
"@types/lokijs": "^1.5.14",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@types/react-router-dom": "^5.3.3",
"@types/react-router-hash-link": "^2.4.5",
"async-lock": "^1.3.1",
"axios": "^0.27.2",
"@types/react-router-hash-link": "^2.4.9",
"async-lock": "^1.4.1",
"ky": "^2.0.2",
"lokijs": "^1.5.12",
"moment": "^2.29.3",
"rc-tree": "^5.6.5",
"react": "^18.2.0",
"react-app-polyfill": "^3.0.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-ga4": "^1.4.1",
"react-helmet-async": "^1.3.0",
"react-image-lightbox": "^5.1.4",
"react-overlays": "^5.2.0",
"react-router-dom": "^6.3.0",
"moment": "^2.30.1",
"rc-tree": "^5.13.1",
"react": "^19.2.6",
"react-cookie": "^8.1.2",
"react-dom": "^19.2.6",
"react-ga4": "^3.0.1",
"react-helmet-async": "^3.0.0",
"react-overlays": "^5.2.1",
"react-router-dom": "^7.15.1",
"react-router-hash-link": "^2.4.3",
"react-scripts": "5.0.1",
"react-spinner-material": "^1.4.0",
"split-string": "^6.1.0",
"typescript": "~4.7.4",
"web-vitals": "^2.1.4",
"xregexp": "^5.1.1"
"xregexp": "^5.1.2",
"yet-another-react-lightbox": "^3.32.0"
},
"resolutions": {
"@svgr/webpack": "^6.2.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"devDependencies": {
"@biomejs/biome": "^2.4.15",
"@eslint/js": "^10.0.1",
"@types/node": "^24.12.4",
"@vitejs/plugin-react": "^6.0.2",
"eslint": "^10.4.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
"typescript": "~6.0.3",
"typescript-eslint": "^8.59.4",
"vite": "^8.0.14"
}
}
-10
View File
@@ -1,10 +0,0 @@
import { render, screen } from "@testing-library/react";
import React from "react";
import App from "./App";
test("renders welcome message", () => {
window.scrollTo = () => {};
render(<App />);
const linkElement = screen.getByText(/NeuroImaging Platform/);
expect(linkElement).toBeInTheDocument();
});
+15 -15
View File
@@ -1,20 +1,20 @@
import React from "react";
import { CookiesProvider } from "react-cookie";
import { HelmetProvider } from "react-helmet-async";
import { BrowserRouter } from "react-router-dom";
import "./App.css";
import AppRoot from "./common/AppRoot";
import type React from 'react';
import { CookiesProvider } from 'react-cookie';
import { HelmetProvider } from 'react-helmet-async';
import { BrowserRouter } from 'react-router-dom';
import './App.css';
import AppRoot from './common/AppRoot';
const App: React.FC = () => {
return (
<BrowserRouter>
<CookiesProvider>
<HelmetProvider>
<AppRoot />
</HelmetProvider>
</CookiesProvider>
</BrowserRouter>
);
return (
<BrowserRouter>
<CookiesProvider>
<HelmetProvider>
<AppRoot />
</HelmetProvider>
</CookiesProvider>
</BrowserRouter>
);
};
export default App;
+23 -23
View File
@@ -1,31 +1,31 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Route, Routes } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import BrainExplorerIndex from "./BrainExplorerIndex";
import type React from 'react';
import { Helmet } from 'react-helmet-async';
import { Route, Routes } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
import Functions from '../functions';
import BrainExplorerIndex from './BrainExplorerIndex';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const BrainExplorer: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<Helmet>
<title>Brain Explorer - {Functions.siteTitle(lang)}</title>
</Helmet>
<Routes>
<Route path="index_jm_eng.php" element={<BrainExplorerIndex lang="en" type="jm" />} />
<Route path="index_jm_jpn.php" element={<BrainExplorerIndex lang="ja" type="jm" />} />
<Route path="index_hu_eng.php" element={<BrainExplorerIndex lang="en" type="hu" />} />
<Route path="index_hu_jpn.php" element={<BrainExplorerIndex lang="ja" type="hu" />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
const { lang } = props;
return (
<>
<Helmet>
<title>Brain Explorer - {Functions.siteTitle(lang)}</title>
</Helmet>
<Routes>
<Route path="index_jm_eng.php" element={<BrainExplorerIndex lang="en" type="jm" />} />
<Route path="index_jm_jpn.php" element={<BrainExplorerIndex lang="ja" type="jm" />} />
<Route path="index_hu_eng.php" element={<BrainExplorerIndex lang="en" type="hu" />} />
<Route path="index_hu_jpn.php" element={<BrainExplorerIndex lang="ja" type="hu" />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
};
export default BrainExplorer;
+26 -26
View File
@@ -1,49 +1,49 @@
.panel {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: white;
z-index: 1;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: white;
z-index: 1;
}
.panel::after {
content: "";
display: block;
clear: both;
content: "";
display: block;
clear: both;
}
.panelNavi {
width: 22%;
height: 100%;
border-right: 2px solid #cccccc;
float: left;
width: 22%;
height: 100%;
border-right: 2px solid #cccccc;
float: left;
}
.panelButtons {
border-bottom: 2px solid #cccccc;
padding: 5px 10px;
border-bottom: 2px solid #cccccc;
padding: 5px 10px;
}
.panelMenu {
padding: 5px 10px;
padding: 5px 10px;
}
.panelMenuAistLink {
text-align: center;
text-align: center;
}
.panelContent {
height: 100%;
overflow: auto;
float: right;
padding: 5px;
width: calc(100% - 22% - 2px);
height: 100%;
overflow: auto;
float: right;
padding: 5px;
width: calc(100% - 22% - 2px);
}
.panelContent iframe {
width: 100%;
height: 100%;
border: 0;
width: 100%;
height: 100%;
border: 0;
}
+97 -94
View File
@@ -1,111 +1,114 @@
import React, { useState } from "react";
import { Modal } from "react-overlays";
import { Link, useLocation } from "react-router-dom";
import { MultiLang } from "../config";
import Functions from "../functions";
import imageLogoAist from "./assets/images/logo_aist.png";
import imageNaviHome from "./assets/images/navi_home.png";
import imageNaviInfo from "./assets/images/navi_info.png";
import styles from "./BrainExplorerIndex.module.css";
import type React from 'react';
import { useState } from 'react';
import { Modal } from 'react-overlays';
import { Link, useLocation } from 'react-router-dom';
import type { MultiLang } from '../config';
import Functions from '../functions';
import imageLogoAist from './assets/images/logo_aist.png';
import imageNaviHome from './assets/images/navi_home.png';
import imageNaviInfo from './assets/images/navi_info.png';
import styles from './BrainExplorerIndex.module.css';
interface Props {
lang: MultiLang;
type: "jm" | "hu";
lang: MultiLang;
type: 'jm' | 'hu';
}
const title = {
en: "Brain Atlas Database of Japanese Monkey for WWW.",
ja: "脳画像データベース",
en: 'Brain Atlas Database of Japanese Monkey for WWW.',
ja: '脳画像データベース',
};
const description = {
en: "Neuroscience researches for elucidation of brain functions require electrophysiological, pharmacological or anatomical operations into the brain. However, no brain atlas of a Japanese monkey, which is used widely in these experiments, was made except detail maps of the brain stem. We have made magnetic resonance images as well as histological sections of a Japanese monkey brain. For the purpose of the practical and efficient use of these images, we will construct computer database for WWW, in which we can display, magnify, and compare these images by easy mouse operations.",
ja: "脳の優れた機能を解明し工学に利用するために、ヒトに近い機能を持った霊長類の神経科学的研究が行われている。脳機能の解明のためには脳の内部に対してさまざまな処置を施す必要がある。例えば、脳の特定の位置に記録、刺激電極を刺入する、薬剤を注入する、局部的に破壊するなどの場合がある。これらの処置に必要な資料は、脳の組織標本である。しかし、研究で良く使われるニホンザルの脳の組織標本は脳幹部を除いては今まで利用できる資料はなかった。そこで脳のMRI(磁気共鳴映像法)連続画像を撮ると共に、高次機能を調べるうえで重要な脳皮質全体を含む組織標本の連続切片を作成した。実験上また文献を読むときなどの資料として有効かつ簡便に活用するために、それらの画像をデジタル化し、マウスの操作のみで簡便に参照できるデータベースを構築し、WWW上で公開する。",
en: 'Neuroscience researches for elucidation of brain functions require electrophysiological, pharmacological or anatomical operations into the brain. However, no brain atlas of a Japanese monkey, which is used widely in these experiments, was made except detail maps of the brain stem. We have made magnetic resonance images as well as histological sections of a Japanese monkey brain. For the purpose of the practical and efficient use of these images, we will construct computer database for WWW, in which we can display, magnify, and compare these images by easy mouse operations.',
ja: '脳の優れた機能を解明し工学に利用するために、ヒトに近い機能を持った霊長類の神経科学的研究が行われている。脳機能の解明のためには脳の内部に対してさまざまな処置を施す必要がある。例えば、脳の特定の位置に記録、刺激電極を刺入する、薬剤を注入する、局部的に破壊するなどの場合がある。これらの処置に必要な資料は、脳の組織標本である。しかし、研究で良く使われるニホンザルの脳の組織標本は脳幹部を除いては今まで利用できる資料はなかった。そこで脳のMRI(磁気共鳴映像法)連続画像を撮ると共に、高次機能を調べるうえで重要な脳皮質全体を含む組織標本の連続切片を作成した。実験上また文献を読むときなどの資料として有効かつ簡便に活用するために、それらの画像をデジタル化し、マウスの操作のみで簡便に参照できるデータベースを構築し、WWW上で公開する。',
};
const BrainExplorerIndex: React.FC<Props> = (props: Props) => {
const { lang, type } = props;
const [showInfo, setShowInfo] = useState(false);
const location = useLocation();
const parentUrl = location.pathname.substring(0, location.pathname.lastIndexOf("/"));
const { lang, type } = props;
const [showInfo, setShowInfo] = useState(false);
const location = useLocation();
const parentUrl = location.pathname.substring(0, location.pathname.lastIndexOf('/'));
const getUrl = (type: string, lang: MultiLang) => {
const label = { en: "eng", ja: "jpn" };
return `${parentUrl}/index_${type}_${label[lang]}.php`;
};
const renderShowInfo = () => {
return (
<div>
<h1>{title[lang]}</h1>
<hr />
<p>{description[lang]}</p>
<hr />
</div>
);
};
const renderBrainExplorerContent = () => {
const url = `./${type === "hu" ? "human" : "jmonkey"}.html?LANG=${lang === "en" ? "ENG" : "JPN"}`;
return <iframe src={url} title="image"></iframe>;
};
const getUrl = (type: string, lang: MultiLang) => {
const label = { en: 'eng', ja: 'jpn' };
return `${parentUrl}/index_${type}_${label[lang]}.php`;
};
const renderShowInfo = () => {
return (
<Modal show={true}>
<div className={styles.panel}>
<div className={styles.panelNavi}>
<div className={styles.panelButtons}>
<Link to="/">
<img src={imageNaviHome} alt="HOME" />
</Link>
&nbsp;
<a
href="/"
onClick={(e) => {
e.preventDefault();
setShowInfo(true);
}}
>
<img src={imageNaviInfo} alt="INFO" />
</a>
</div>
<div className={styles.panelMenu}>
<h3>Brain Explorer [MRI]</h3>
<ul>
<li>
<Link to={getUrl("jm", lang)} onClick={() => setShowInfo(false)}>
{Functions.mlang("[en]Japanese Monkey[/en][ja]ニホンザル[/ja]", lang)}
<br />
<em>(Macaca fuscata)</em>
<br />
{Functions.mlang("[en]Developmental Data[/en][ja]発達の様子[/ja]", lang)}
</Link>
</li>
<li>
<Link to={getUrl("hu", lang)} onClick={() => setShowInfo(false)}>
{Functions.mlang("[en]Human[/en][ja]ヒト[/ja]", lang)}
<br />
<em>(Homo sapiens)</em>
</Link>
</li>
</ul>
<hr />
<ul>
<li>
<Link to={getUrl(type, lang === "en" ? "ja" : "en")}>{Functions.mlang("[en]Japanese page.[/en][ja]English page.[/ja]", lang)}</Link>
</li>
</ul>
<hr />
<div className={styles.panelMenuAistLink}>
<a href="http://www.aist.go.jp/" target="_blank" rel="noopener noreferrer">
<img src={imageLogoAist} alt="AIST" />
</a>
</div>
</div>
</div>
<div className={styles.panelContent}>{showInfo ? renderShowInfo() : renderBrainExplorerContent()}</div>
</div>
</Modal>
<div>
<h1>{title[lang]}</h1>
<hr />
<p>{description[lang]}</p>
<hr />
</div>
);
};
const renderBrainExplorerContent = () => {
const url = `./${type === 'hu' ? 'human' : 'jmonkey'}.html?LANG=${lang === 'en' ? 'ENG' : 'JPN'}`;
return <iframe src={url} title="image"></iframe>;
};
return (
<Modal show={true}>
<div className={styles.panel}>
<div className={styles.panelNavi}>
<div className={styles.panelButtons}>
<Link to="/">
<img src={imageNaviHome} alt="HOME" />
</Link>
&nbsp;
<a
href="/"
onClick={(e) => {
e.preventDefault();
setShowInfo(true);
}}
>
<img src={imageNaviInfo} alt="INFO" />
</a>
</div>
<div className={styles.panelMenu}>
<h3>Brain Explorer [MRI]</h3>
<ul>
<li>
<Link to={getUrl('jm', lang)} onClick={() => setShowInfo(false)}>
{Functions.mlang('[en]Japanese Monkey[/en][ja]ニホンザル[/ja]', lang)}
<br />
<em>(Macaca fuscata)</em>
<br />
{Functions.mlang('[en]Developmental Data[/en][ja]発達の様子[/ja]', lang)}
</Link>
</li>
<li>
<Link to={getUrl('hu', lang)} onClick={() => setShowInfo(false)}>
{Functions.mlang('[en]Human[/en][ja]ヒト[/ja]', lang)}
<br />
<em>(Homo sapiens)</em>
</Link>
</li>
</ul>
<hr />
<ul>
<li>
<Link to={getUrl(type, lang === 'en' ? 'ja' : 'en')}>
{Functions.mlang('[en]Japanese page.[/en][ja]English page.[/ja]', lang)}
</Link>
</li>
</ul>
<hr />
<div className={styles.panelMenuAistLink}>
<a href="http://www.aist.go.jp/" target="_blank" rel="noopener noreferrer">
<img src={imageLogoAist} alt="AIST" />
</a>
</div>
</div>
</div>
<div className={styles.panelContent}>{showInfo ? renderShowInfo() : renderBrainExplorerContent()}</div>
</div>
</Modal>
);
};
export default BrainExplorerIndex;
+1018 -957
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+32 -30
View File
@@ -1,41 +1,43 @@
import React, { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
import ReactGA from "react-ga4";
import { useLocation } from "react-router-dom";
import Config, { MultiLang } from "../config";
import Page from "../custom/Page";
import type React from 'react';
import { useEffect, useState } from 'react';
import { useCookies } from 'react-cookie';
import ReactGA from 'react-ga4';
import { useLocation } from 'react-router-dom';
import Config, { type MultiLang } from '../config';
import Page from '../custom/Page';
const AppMain: React.FC = () => {
const [cookies, setCookie] = useCookies();
const [lang, setLang] = useState<MultiLang>(["en", "ja"].includes(cookies.ml_lang) ? cookies.ml_lang : "en");
const location = useLocation();
const [cookies, setCookie] = useCookies();
const [lang, setLang] = useState<MultiLang>(['en', 'ja'].includes(cookies.ml_lang) ? cookies.ml_lang : 'en');
const location = useLocation();
useEffect(() => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") {
ReactGA.send("pageview");
}
const params = new URLSearchParams(location.search);
const ml_lang = params.get("ml_lang");
if (ml_lang != null && ["en", "ja"].includes(ml_lang)) {
if (cookies.ml_lang !== ml_lang) {
setCookie("ml_lang", ml_lang);
}
if (lang !== ml_lang) {
setLang(ml_lang as MultiLang);
}
}
window.scrollTo(0, 0);
}, [cookies, setCookie, lang, location]);
useEffect(() => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') {
ReactGA.send('pageview');
}
const params = new URLSearchParams(location.search);
const ml_lang = params.get('ml_lang');
if (ml_lang != null && ['en', 'ja'].includes(ml_lang)) {
if (cookies.ml_lang !== ml_lang) {
setCookie('ml_lang', ml_lang);
}
if (lang !== ml_lang) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setLang(ml_lang as MultiLang);
}
}
window.scrollTo(0, 0);
}, [cookies, setCookie, lang, location]);
return <Page lang={lang} />;
return <Page lang={lang} />;
};
const AppRoot: React.FC = () => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") {
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID);
}
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') {
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID);
}
return <AppMain />;
return <AppMain />;
};
export default AppRoot;
+25 -25
View File
@@ -1,35 +1,35 @@
import React from "react";
import { Navigate } from "react-router";
import { useLocation } from "react-router-dom";
import { MultiLang } from "../config";
import PageNotFound from "./lib/PageNotFound";
import type React from 'react';
import { Navigate } from 'react-router';
import { useLocation } from 'react-router-dom';
import type { MultiLang } from '../config';
import PageNotFound from './lib/PageNotFound';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoopsPathRedirect: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const { lang } = props;
const location = useLocation();
const getRedirectUrl = () => {
const { pathname } = location;
switch (pathname || "") {
case "/index.php": {
return "/";
}
}
return "";
};
const getRedirectUrl = () => {
const { pathname } = location;
switch (pathname || '') {
case '/index.php': {
return '/';
}
}
return '';
};
if (location.pathname === "/") {
return null;
}
const url = getRedirectUrl();
if (url === "") {
return <PageNotFound lang={lang} />;
}
return <Navigate to={url} />;
if (location.pathname === '/') {
return null;
}
const url = getRedirectUrl();
if (url === '') {
return <PageNotFound lang={lang} />;
}
return <Navigate to={url} />;
};
export default XoopsPathRedirect;
+62 -17
View File
@@ -1,17 +1,62 @@
img {border: 0;}
#xoopsHiddenText {visibility: hidden; color: #000000; font-weight: normal; font-style: normal; text-decoration: none;}
.pagneutral {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/pagneutral.gif);}
.pagact {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/pagact.gif);}
.paginact {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/paginact.gif);}
#mainmenu a {text-align:left; display: block; margin: 0; padding: 4px;}
#mainmenu a.menuTop {padding-left: 3px;}
#mainmenu a.menuMain {padding-left: 3px;}
#mainmenu a.menuSub {padding-left: 9px;}
#usermenu a {text-align:left; display: block; margin: 0; padding: 4px;}
#usermenu a.menuTop {}
#usermenu a.highlight {color: #0000ff; background-color: #fcc;}
img {
border: 0;
}
#xoopsHiddenText {
visibility: hidden;
color: #000000;
font-weight: normal;
font-style: normal;
text-decoration: none;
}
.pagneutral {
font-size: 10px;
width: 16px;
height: 19px;
text-align: center;
background-image: url(./images/pagneutral.gif);
}
.pagact {
font-size: 10px;
width: 16px;
height: 19px;
text-align: center;
background-image: url(./images/pagact.gif);
}
.paginact {
font-size: 10px;
width: 16px;
height: 19px;
text-align: center;
background-image: url(./images/paginact.gif);
}
#mainmenu a {
text-align: left;
display: block;
margin: 0;
padding: 4px;
}
#mainmenu a.menuTop {
padding-left: 3px;
}
#mainmenu a.menuMain {
padding-left: 3px;
}
#mainmenu a.menuSub {
padding-left: 9px;
}
#usermenu a {
text-align: left;
display: block;
margin: 0;
padding: 4px;
}
#usermenu a.menuTop {
}
#usermenu a.highlight {
color: #0000ff;
background-color: #fcc;
}
+22 -22
View File
@@ -1,33 +1,33 @@
import React from "react";
import { Link, useLocation } from "react-router-dom";
import { MultiLang } from "../../config";
import mlangEnglish from "../assets/images/mlang_english.gif";
import mlangJapanese from "../assets/images/mlang_japanese.gif";
import type React from 'react';
import { Link, useLocation } from 'react-router-dom';
import type { MultiLang } from '../../config';
import mlangEnglish from '../assets/images/mlang_english.gif';
import mlangJapanese from '../assets/images/mlang_japanese.gif';
interface Props {
lang: MultiLang;
className?: string;
image?: string;
lang: MultiLang;
className?: string;
image?: string;
}
const langResources = {
en: { image: mlangEnglish, title: "English" },
ja: { image: mlangJapanese, title: "Japanese" },
en: { image: mlangEnglish, title: 'English' },
ja: { image: mlangJapanese, title: 'Japanese' },
};
const LangFlag: React.FC<Props> = (props: Props) => {
const { lang, className, image } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const flagLang = lang === "en" ? "ja" : "en";
params.set("ml_lang", flagLang);
const url = location.pathname + "?" + params.toString();
const imageSrc = image || langResources[flagLang].image;
return (
<Link className={className} to={url}>
<img src={imageSrc} alt={langResources[flagLang].title} title={langResources[flagLang].title} />
</Link>
);
const { lang, className, image } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const flagLang = lang === 'en' ? 'ja' : 'en';
params.set('ml_lang', flagLang);
const url = location.pathname + '?' + params.toString();
const imageSrc = image || langResources[flagLang].image;
return (
<Link className={className} to={url}>
<img src={imageSrc} alt={langResources[flagLang].title} title={langResources[flagLang].title} />
</Link>
);
};
export default LangFlag;
+7 -7
View File
@@ -1,12 +1,12 @@
import React from "react";
import Spinner from "react-spinner-material";
import type React from 'react';
import Spinner from 'react-spinner-material';
const Loading: React.FC = () => {
return (
<div style={{ display: "flex", justifyContent: "center", alignContent: "center", margin: "100px 0" }}>
<Spinner radius={60} color={"#cccccc"} stroke={8} visible={true} />
</div>
);
return (
<div style={{ display: 'flex', justifyContent: 'center', alignContent: 'center', margin: '100px 0' }}>
<Spinner radius={60} color={'#cccccc'} stroke={8} visible={true} />
</div>
);
};
export default Loading;
+8 -7
View File
@@ -1,15 +1,16 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const NoticeSiteHasBeenArchived: React.FC<Props> = (props: Props) => {
const { lang } = props;
const notice = "[en]This site has been archived since FY2019 and is no longer updated.[/en][ja]このサイトは、2019年度よりアーカイブサイトとして運用されています。[/ja]";
return <p style={{ color: "red" }}>{Functions.mlang(notice, lang)}</p>;
const { lang } = props;
const notice =
'[en]This site has been archived since FY2019 and is no longer updated.[/en][ja]このサイトは、2019年度よりアーカイブサイトとして運用されています。[/ja]';
return <p style={{ color: 'red' }}>{Functions.mlang(notice, lang)}</p>;
};
export default NoticeSiteHasBeenArchived;
+41 -40
View File
@@ -1,50 +1,51 @@
import React, { useEffect } from "react";
import { Helmet } from "react-helmet-async";
import { useLocation, useNavigate } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import { useEffect } from 'react';
import { Helmet } from 'react-helmet-async';
import { useLocation, useNavigate } from 'react-router-dom';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const PageNotFound: React.FC<Props> = (props) => {
const { lang } = props;
const location = useLocation();
const navigate = useNavigate();
const url = "/";
const { lang } = props;
const location = useLocation();
const navigate = useNavigate();
const url = '/';
useEffect(() => {
let timer: NodeJS.Timeout | null = null;
if (location.pathname !== "/" && process.env.NODE_ENV === "production") {
if (timer === null) {
timer = setTimeout(() => {
navigate(url);
}, 5000);
}
}
return () => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
};
}, [location, navigate]);
useEffect(() => {
let timer: ReturnType<typeof setTimeout> | null = null;
if (location.pathname !== '/' && import.meta.env.PROD) {
if (timer === null) {
timer = setTimeout(() => {
navigate(url);
}, 5000);
}
}
return () => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
};
}, [location, navigate]);
return (
<div>
<Helmet>
<title>Page Not Found - {Functions.siteTitle(lang)}</title>
</Helmet>
<h1>Page Not Found</h1>
<section>
<p>The page you were trying to access doesn't exist.</p>
<p>
If the page does not automatically reload, please click <a href={url}>here</a>
</p>
</section>
</div>
);
return (
<div>
<Helmet>
<title>Page Not Found - {Functions.siteTitle(lang)}</title>
</Helmet>
<h1>Page Not Found</h1>
<section>
<p>The page you were trying to access doesn't exist.</p>
<p>
If the page does not automatically reload, please click <a href={url}>here</a>
</p>
</section>
</div>
);
};
export default PageNotFound;
+118 -100
View File
@@ -1,132 +1,150 @@
import ReactHtmlParser, { convertNodeToElement, DomElement, DomNode, Transform } from "@orrisroot/react-html-parser";
import React from "react";
import { HashLink } from "react-router-hash-link";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import ReactHtmlParser, {
convertNodeToElement,
type DomElement,
type DomNode,
type Transform,
} from '@orrisroot/react-html-parser';
import type React from 'react';
import { HashLink } from 'react-router-hash-link';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
text: string;
dohtml?: boolean;
dosmiley?: boolean;
doxcode?: boolean;
doimage?: boolean;
dobr?: boolean;
lang: MultiLang;
text: string;
dohtml?: boolean;
dosmiley?: boolean;
doxcode?: boolean;
doimage?: boolean;
dobr?: boolean;
}
const preConvertXCode = (text: string, doxcode: boolean): string => {
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (m0, m1) => {
return "[code]" + Functions.base64Encode(m1) + "[/code]";
});
}
return text;
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (_m0, m1) => {
return '[code]' + Functions.base64Encode(m1) + '[/code]';
});
}
return text;
};
const postConvertXCode = (text: string, doxcode: boolean, doimage: boolean): string => {
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (m0, m1) => {
const text = convertXCode(Functions.htmlspecialchars(Functions.base64Decode(m1)), doimage);
return '<div class="xoopsCode"><pre><code>' + text + "</code></pre></div>";
});
}
return text;
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (_m0, m1) => {
const text = convertXCode(Functions.htmlspecialchars(Functions.base64Decode(m1)), doimage);
return '<div class="xoopsCode"><pre><code>' + text + '</code></pre></div>';
});
}
return text;
};
const convertClickable = (text: string) => {
text = text.replace(/(^|[^\]_a-zA-Z0-9-="'/]+)((?:https?|ftp)(?::\/\/[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+[a-zA-Z0-9=]))/g, (...matches) => {
return matches[1] + '<a href="' + matches[2] + '" target="_blank" rel="external noopener noreferrer">' + matches[2] + "</a>";
});
text = text.replace(/(^|[^\]_a-zA-Z0-9-="'/:.]+)([a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+)/g, (...matches) => {
return matches[1] + '<a href="mailto:' + matches[2] + '">' + matches[2] + "</a>";
});
return text;
text = text.replace(
/(^|[^\]_a-zA-Z0-9-="'/]+)((?:https?|ftp)(?::\/\/[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+[a-zA-Z0-9=]))/g,
(...matches) => {
return (
matches[1] +
'<a href="' +
matches[2] +
'" target="_blank" rel="external noopener noreferrer">' +
matches[2] +
'</a>'
);
},
);
text = text.replace(
/(^|[^\]_a-zA-Z0-9-="'/:.]+)([a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+)/g,
(...matches) => {
return matches[1] + '<a href="mailto:' + matches[2] + '">' + matches[2] + '</a>';
},
);
return text;
};
const convertXCode = (text: string, doimage: boolean): string => {
// TODO: implement
return text;
const convertXCode = (text: string, _doimage: boolean): string => {
// TODO: implement
return text;
};
const convertSmiley = (text: string) => {
// TODO: implement
return text;
// TODO: implement
return text;
};
const convertBr = (text: string): string => {
return text.replace(/(\r?\n|\r)/g, "<br />");
return text.replace(/(\r?\n|\r)/g, '<br />');
};
const cssConvert = (text: string): object => {
const ret: any = {};
text.split(";").forEach((line) => {
const line_ = line.trim();
if (line.length === 0) {
return;
}
const kv = line_.split(":");
const key = Functions.camelCase(kv[0].trim());
const value = kv[1].trim();
ret[key] = value;
});
return ret;
const ret: any = {};
text.split(';').forEach((line) => {
const line_ = line.trim();
if (line.length === 0) {
return;
}
const kv = line_.split(':');
const key = Functions.camelCase(kv[0].trim());
const value = kv[1].trim();
ret[key] = value;
});
return ret;
};
const xoopsTransform: Transform = (node: DomNode, index: number | string, transform?: Transform): React.ReactNode => {
if (node.type === "tag") {
const node_ = node as DomElement;
if (node_.name === "a") {
const url = node_.attribs?.["href"] || "/";
const download = (node_.attribs && node_.attribs["download"]) || "";
const rel = (node_.attribs && node_.attribs["rel"]) || "";
const klass = (node_.attribs && node_.attribs["class"]) || "";
const isFile = download !== "" || /\.(zip|pdf|png|gif|jpg)$/.test(url);
const isExternal = /external/.test(rel) || /external/.test(klass) || /^(mailto|https?:?\/\/)/.test(url);
if (!isFile && !isExternal) {
const style = node_.attribs?.["style"] || "";
const title = node_.attribs?.["title"];
return (
<HashLink key={index} to={url} style={cssConvert(style)} title={title}>
{node_.children.map((value: DomNode, index: number) => {
return convertNodeToElement(value, index, transform);
})}
</HashLink>
);
}
}
if (node_.name === "img") {
const src = (node_.attribs && node_.attribs["src"]) || "";
node_.attribs["src"] = src.replace("`XOOPS_URL`", process.env.PUBLIC_URL);
return convertNodeToElement(node_, index, transform);
}
if (node.type === 'tag') {
const node_ = node as DomElement;
if (node_.name === 'a') {
const url = node_.attribs?.['href'] || '/';
const download = (node_.attribs && node_.attribs['download']) || '';
const rel = (node_.attribs && node_.attribs['rel']) || '';
const klass = (node_.attribs && node_.attribs['class']) || '';
const isFile = download !== '' || /\.(zip|pdf|png|gif|jpg)$/.test(url);
const isExternal = /external/.test(rel) || /external/.test(klass) || /^(mailto|https?:?\/\/)/.test(url);
if (!isFile && !isExternal) {
const style = node_.attribs?.['style'] || '';
const title = node_.attribs?.['title'];
return (
<HashLink key={index} to={url} style={cssConvert(style)} title={title}>
{node_.children.map((value: DomNode, index: number) => {
return convertNodeToElement(value, index, transform);
})}
</HashLink>
);
}
}
if (node_.name === 'img') {
const src = (node_.attribs && node_.attribs['src']) || '';
node_.attribs['src'] = src.replace('`XOOPS_URL`', '');
return convertNodeToElement(node_, index, transform);
}
}
};
const XoopsCode: React.FC<Props> = (props: Props) => {
const { lang } = props;
let text = props.text;
const dohtml = !!props.dohtml;
const dosmiley = !!props.dosmiley;
const doxcode = !!props.doxcode;
const doimage = !!props.doimage;
const dobr = !!props.dobr;
text = preConvertXCode(text, doxcode);
if (!dohtml) {
text = Functions.htmlspecialchars(text);
text = convertClickable(text);
}
if (dosmiley) {
text = convertSmiley(text);
}
if (doxcode) {
text = convertXCode(text, doimage);
}
if (dobr) {
text = convertBr(text);
}
text = postConvertXCode(text, doxcode, doimage);
text = Functions.mlang(text, lang);
return <div>{ReactHtmlParser(text, { transform: xoopsTransform })}</div>;
const { lang } = props;
let text = props.text;
const dohtml = !!props.dohtml;
const dosmiley = !!props.dosmiley;
const doxcode = !!props.doxcode;
const doimage = !!props.doimage;
const dobr = !!props.dobr;
text = preConvertXCode(text, doxcode);
if (!dohtml) {
text = Functions.htmlspecialchars(text);
text = convertClickable(text);
}
if (dosmiley) {
text = convertSmiley(text);
}
if (doxcode) {
text = convertXCode(text, doimage);
}
if (dobr) {
text = convertBr(text);
}
text = postConvertXCode(text, doxcode, doimage);
text = Functions.mlang(text, lang);
return <div>{ReactHtmlParser(text, { transform: xoopsTransform })}</div>;
};
export default XoopsCode;
+9 -9
View File
@@ -1,15 +1,15 @@
const SITE_TITLE = "NeuroImaging Platform";
const SITE_SLOGAN = "";
const GOOGLE_ANALYTICS_TRACKING_ID = "UA-3757403-1";
const XOONIPS_ITEMTYPES = ["book", "paper", "presentation", "data", "tool", "nimgcenter", "model", "url"];
const SITE_TITLE = 'NeuroImaging Platform';
const SITE_SLOGAN = '';
const GOOGLE_ANALYTICS_TRACKING_ID = 'UA-3757403-1';
const XOONIPS_ITEMTYPES = ['book', 'paper', 'presentation', 'data', 'tool', 'nimgcenter', 'model', 'url'];
export type MultiLang = "en" | "ja";
export type MultiLang = 'en' | 'ja';
const Config = {
SITE_TITLE,
SITE_SLOGAN,
GOOGLE_ANALYTICS_TRACKING_ID,
XOONIPS_ITEMTYPES,
SITE_TITLE,
SITE_SLOGAN,
GOOGLE_ANALYTICS_TRACKING_ID,
XOONIPS_ITEMTYPES,
};
export default Config;
+33 -33
View File
@@ -1,47 +1,47 @@
import React from "react";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import CreditsIndex from "./CreditsIndex";
import CreditsPage from "./CreditsPage";
import type React from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
import CreditsIndex from './CreditsIndex';
import CreditsPage from './CreditsPage';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const CreditsTop: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const paramId = params.get("id");
if (null === paramId) {
return <CreditsIndex lang={lang} />;
}
if (/^[1-9][0-9]*$/.test(paramId)) {
const id = parseInt(paramId, 10);
return <CreditsPage lang={lang} id={id} />;
}
return <PageNotFound lang={lang} />;
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const paramId = params.get('id');
if (null === paramId) {
return <CreditsIndex lang={lang} />;
}
if (/^[1-9][0-9]*$/.test(paramId)) {
const id = parseInt(paramId, 10);
return <CreditsPage lang={lang} id={id} />;
}
return <PageNotFound lang={lang} />;
};
const CreditsRedirectToTop: React.FC = () => {
const location = useLocation();
const url = location.pathname + "index.php" + location.search + location.hash;
return <Navigate to={url} />;
const location = useLocation();
const url = location.pathname + 'index.php' + location.search + location.hash;
return <Navigate to={url} />;
};
const Credits: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<Routes>
<Route path="" element={<CreditsRedirectToTop />} />
<Route path="index.php" element={<CreditsTop lang={lang} />} />
<Route path="aboutus.php" element={<CreditsPage lang={lang} id={0} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
const { lang } = props;
return (
<>
<Routes>
<Route path="" element={<CreditsRedirectToTop />} />
<Route path="index.php" element={<CreditsTop lang={lang} />} />
<Route path="aboutus.php" element={<CreditsPage lang={lang} id={0} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
};
export default Credits;
+51 -50
View File
@@ -1,59 +1,60 @@
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { Link } from "react-router-dom";
import Loading from "../common/lib/Loading";
import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import CreditsUtils, { CreditsIndexData } from "./lib/CreditsUtils";
import type React from 'react';
import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { Link } from 'react-router-dom';
import Loading from '../common/lib/Loading';
import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
import Functions from '../functions';
import CreditsUtils, { type CreditsIndexData } from './lib/CreditsUtils';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const CreditsIndex: React.FC<Props> = (props: Props) => {
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [index, setIndex] = useState<CreditsIndexData | null>(null);
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [index, setIndex] = useState<CreditsIndexData | null>(null);
useEffect(() => {
const updatePage = async () => {
const index = await CreditsUtils.getIndex();
setLoading(false);
setIndex(index);
};
updatePage();
}, [lang]);
useEffect(() => {
const updatePage = async () => {
const index = await CreditsUtils.getIndex();
setLoading(false);
setIndex(index);
};
updatePage();
}, [lang]);
if (loading) {
return <Loading />;
}
if (null === index) {
return <PageNotFound lang={lang} />;
}
const mtitle = Functions.mlang(index.name, lang);
return (
<>
<Helmet>
<title>
{mtitle} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{mtitle}</h3>
<NoticeSiteHasBeenArchived lang={lang} />
<ul>
{index.pages.map((page) => {
const title = Functions.mlang(page.title, lang);
const url = CreditsUtils.getPageUrl(page.id);
return (
<li key={page.id}>
<Link to={url}>{title}</Link>
</li>
);
})}
</ul>
</>
);
if (loading) {
return <Loading />;
}
if (null === index) {
return <PageNotFound lang={lang} />;
}
const mtitle = Functions.mlang(index.name, lang);
return (
<>
<Helmet>
<title>
{mtitle} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{mtitle}</h3>
<NoticeSiteHasBeenArchived lang={lang} />
<ul>
{index.pages.map((page) => {
const title = Functions.mlang(page.title, lang);
const url = CreditsUtils.getPageUrl(page.id);
return (
<li key={page.id}>
<Link to={url}>{title}</Link>
</li>
);
})}
</ul>
</>
);
};
export default CreditsIndex;
+53 -52
View File
@@ -1,62 +1,63 @@
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import Loading from "../common/lib/Loading";
import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived";
import PageNotFound from "../common/lib/PageNotFound";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import Functions from "../functions";
import CreditsUtils, { CreditsIndexData, CreditsPageDetailData } from "./lib/CreditsUtils";
import type React from 'react';
import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import Loading from '../common/lib/Loading';
import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived';
import PageNotFound from '../common/lib/PageNotFound';
import XoopsCode from '../common/lib/XoopsCode';
import type { MultiLang } from '../config';
import Functions from '../functions';
import CreditsUtils, { type CreditsIndexData, type CreditsPageDetailData } from './lib/CreditsUtils';
interface Props {
lang: MultiLang;
id: number;
lang: MultiLang;
id: number;
}
const CreditsPage: React.FC<Props> = (props: Props) => {
const { lang, id } = props;
const [loading, setLoading] = useState<boolean>(true);
const [index, setIndex] = useState<CreditsIndexData | null>(null);
const [page, setPage] = useState<CreditsPageDetailData | null>(null);
const { lang, id } = props;
const [loading, setLoading] = useState<boolean>(true);
const [index, setIndex] = useState<CreditsIndexData | null>(null);
const [page, setPage] = useState<CreditsPageDetailData | null>(null);
useEffect(() => {
const updatePage = async () => {
const index = await CreditsUtils.getIndex();
const page = await CreditsUtils.getPage(id);
setLoading(false);
setIndex(index);
setPage(page);
};
updatePage();
}, [id]);
useEffect(() => {
const updatePage = async () => {
const index = await CreditsUtils.getIndex();
const page = await CreditsUtils.getPage(id);
setLoading(false);
setIndex(index);
setPage(page);
};
updatePage();
}, [id]);
if (loading) {
return <Loading />;
}
if (page === null || index === null) {
return <PageNotFound lang={lang} />;
}
if (loading) {
return <Loading />;
}
if (page === null || index === null) {
return <PageNotFound lang={lang} />;
}
const mtitle = Functions.mlang(index.name, lang);
const title = Functions.mlang(page.title, lang);
const lastupdate = page.lastupdate === 0 ? "" : Functions.formatDate(page.lastupdate, "MMMM Do, YYYY");
return (
<>
<Helmet>
<title>
{title} - {mtitle} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{title}</h3>
<NoticeSiteHasBeenArchived lang={lang} />
{"" !== lastupdate && (
<div style={{ textAlign: "right" }}>
{Functions.mlang("[en]Last Update[/en][ja]最終更新日[/ja]", lang)} : {lastupdate}
</div>
)}
<hr />
<XoopsCode lang={lang} text={page.content} dohtml={true} />
</>
);
const mtitle = Functions.mlang(index.name, lang);
const title = Functions.mlang(page.title, lang);
const lastupdate = page.lastupdate === 0 ? '' : Functions.formatDate(page.lastupdate, 'MMMM Do, YYYY');
return (
<>
<Helmet>
<title>
{title} - {mtitle} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{title}</h3>
<NoticeSiteHasBeenArchived lang={lang} />
{'' !== lastupdate && (
<div style={{ textAlign: 'right' }}>
{Functions.mlang('[en]Last Update[/en][ja]最終更新日[/ja]', lang)} : {lastupdate}
</div>
)}
<hr />
<XoopsCode lang={lang} text={page.content} dohtml={true} />
</>
);
};
export default CreditsPage;
+38 -37
View File
@@ -1,47 +1,48 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Loading from "../../common/lib/Loading";
import PageNotFound from "../../common/lib/PageNotFound";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import CreditsUtils, { CreditsMenuData } from "../lib/CreditsUtils";
import type React from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import Loading from '../../common/lib/Loading';
import PageNotFound from '../../common/lib/PageNotFound';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import CreditsUtils, { type CreditsMenuData } from '../lib/CreditsUtils';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const CreditsMenu: React.FC<Props> = (props: Props) => {
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [menu, setMenu] = useState<CreditsMenuData[] | null>(null);
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [menu, setMenu] = useState<CreditsMenuData[] | null>(null);
useEffect(() => {
const updatePage = async () => {
const menu = await CreditsUtils.getMenu();
setLoading(false);
setMenu(menu);
};
updatePage();
}, [lang]);
useEffect(() => {
const updatePage = async () => {
const menu = await CreditsUtils.getMenu();
setLoading(false);
setMenu(menu);
};
updatePage();
}, [lang]);
if (loading) {
return <Loading />;
}
if (menu === null || menu.length === 0) {
return <PageNotFound lang={lang} />;
}
const links = menu.map((item, idx) => {
const title = Functions.mlang(item.title, lang);
const style = idx === 0 ? "menuTop" : "menuMain";
return (
<li key={idx}>
<Link className={style} to={item.link}>
{title}
</Link>
</li>
);
});
return <ul className="mainmenu">{links}</ul>;
if (loading) {
return <Loading />;
}
if (menu === null || menu.length === 0) {
return <PageNotFound lang={lang} />;
}
const links = menu.map((item, idx) => {
const title = Functions.mlang(item.title, lang);
const style = idx === 0 ? 'menuTop' : 'menuMain';
return (
<li key={idx}>
<Link className={style} to={item.link}>
{title}
</Link>
</li>
);
});
return <ul className="mainmenu">{links}</ul>;
};
export default CreditsMenu;
+50 -52
View File
@@ -1,77 +1,75 @@
import axios from "axios";
import ky from 'ky';
export interface CreditsPageData {
id: number;
title: string;
id: number;
title: string;
}
export interface CreditsPageDetailData extends CreditsPageData {
content: string;
lastupdate: number;
content: string;
lastupdate: number;
}
export interface CreditsIndexData {
name: string;
pages: CreditsPageData[];
name: string;
pages: CreditsPageData[];
}
export interface CreditsMenuData {
title: string;
link: string;
title: string;
link: string;
}
class CreditsUtils {
private pathname: string;
private index?: CreditsIndexData | null;
private pathname: string;
private index?: CreditsIndexData | null;
constructor(path: string) {
this.pathname = path;
}
constructor(path: string) {
this.pathname = path;
}
getIndexUrl(): string {
return this.pathname + "/";
}
getIndexUrl(): string {
return this.pathname + '/';
}
getPageUrl(id: number): string {
return this.getIndexUrl() + (0 === id ? "aboutus.php" : "?id=" + id);
}
getPageUrl(id: number): string {
return this.getIndexUrl() + (0 === id ? 'aboutus.php' : '?id=' + id);
}
async getIndex(): Promise<CreditsIndexData | null> {
if (typeof this.index === "undefined") {
try {
const url = this.getIndexUrl() + "index.json";
const response = await axios.get(url);
const index = response.data as CreditsIndexData;
this.index = index;
} catch (err) {
this.index = null;
}
}
return this.index;
async getIndex(): Promise<CreditsIndexData | null> {
if (typeof this.index === 'undefined') {
try {
const url = this.getIndexUrl() + 'index.json';
const index = (await ky.get(url).json()) as CreditsIndexData;
this.index = index;
} catch (_err) {
this.index = null;
}
}
return this.index;
}
async getMenu(): Promise<CreditsMenuData[]> {
const menu: CreditsMenuData[] = [];
const index = await this.getIndex();
if (index !== null) {
index.pages.forEach((page) => {
menu.push({ title: page.title, link: this.getPageUrl(page.id) });
});
}
return menu;
async getMenu(): Promise<CreditsMenuData[]> {
const menu: CreditsMenuData[] = [];
const index = await this.getIndex();
if (index !== null) {
index.pages.forEach((page) => {
menu.push({ title: page.title, link: this.getPageUrl(page.id) });
});
}
return menu;
}
async getPage(id: number): Promise<CreditsPageDetailData | null> {
let page = null;
try {
const url = this.getIndexUrl() + id + ".json";
const response = await axios.get(url);
page = response.data as CreditsPageDetailData;
} catch (err) {
// ignore
}
return page;
async getPage(id: number): Promise<CreditsPageDetailData | null> {
let page = null;
try {
const url = this.getIndexUrl() + id + '.json';
page = (await ky.get(url).json()) as CreditsPageDetailData;
} catch (_err) {
// ignore
}
return page;
}
}
export default new CreditsUtils("/modules/credits");
export default new CreditsUtils('/modules/credits');
+24
View File
@@ -0,0 +1,24 @@
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
+55 -45
View File
@@ -1,52 +1,62 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { HashLink } from "react-router-hash-link";
import { MultiLang } from "../config";
import imageBannerNiu from "./assets/images/banner-niu.png";
import imageBannerRikenCbs from "./assets/images/banner-riken-cbs.png";
import imageBannerRiken from "./assets/images/banner-riken.png";
import imageBannerXoonips from "./assets/images/banner-xoonips.png";
import type React from 'react';
import { useLocation } from 'react-router-dom';
import { HashLink } from 'react-router-hash-link';
import type { MultiLang } from '../config';
import imageBannerNiu from './assets/images/banner-niu.png';
import imageBannerRiken from './assets/images/banner-riken.png';
import imageBannerRikenCbs from './assets/images/banner-riken-cbs.png';
import imageBannerXoonips from './assets/images/banner-xoonips.png';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const Footer: React.FC<Props> = (props: Props) => {
const location = useLocation();
return (
<footer id="footer">
<div className="pageTop">
<HashLink to={`${location.pathname}${location.search}#page`}>
<span className="hidden">Go Page Top</span>
</HashLink>
</div>
<div className="link">
<span>
<a href="http://www.riken.jp/" title="RIKEN" target="_blank" rel="noopener noreferrer">
<img src={imageBannerRiken} alt="RIKEN" />
</a>
</span>
<span>
<a href="https://cbs.riken.jp/" title="RIKEN Center for Brain Science" target="_blank" rel="noopener noreferrer">
<img src={imageBannerRikenCbs} alt="RIKEN Center for Brain Science" />
</a>
</span>
<span>
<a href="https://www.ni.riken.jp/" title="Neuroinformatics Unit, RIKEN Center for Brain Science" target="_blank" rel="noopener noreferrer">
<img src={imageBannerNiu} alt="Neuroinformatics Unit, RIKEN Center for Brain Science" />
</a>
</span>
<span>
<a href="http://xoonips.osdn.jp/" title="XooNIps" target="_blank" rel="noopener noreferrer">
<img src={imageBannerXoonips} alt="XooNIps" />
</a>
</span>
</div>
<div className="copyright">
<span>Copyright (C) 2018 Neuroinformatics Unit, RIKEN Center for Brain Science</span>
</div>
</footer>
);
const Footer: React.FC<Props> = (_props: Props) => {
const location = useLocation();
return (
<footer id="footer">
<div className="pageTop">
<HashLink to={`${location.pathname}${location.search}#page`}>
<span className="hidden">Go Page Top</span>
</HashLink>
</div>
<div className="link">
<span>
<a href="http://www.riken.jp/" title="RIKEN" target="_blank" rel="noopener noreferrer">
<img src={imageBannerRiken} alt="RIKEN" />
</a>
</span>
<span>
<a
href="https://cbs.riken.jp/"
title="RIKEN Center for Brain Science"
target="_blank"
rel="noopener noreferrer"
>
<img src={imageBannerRikenCbs} alt="RIKEN Center for Brain Science" />
</a>
</span>
<span>
<a
href="https://www.ni.riken.jp/"
title="Neuroinformatics Unit, RIKEN Center for Brain Science"
target="_blank"
rel="noopener noreferrer"
>
<img src={imageBannerNiu} alt="Neuroinformatics Unit, RIKEN Center for Brain Science" />
</a>
</span>
<span>
<a href="http://xoonips.osdn.jp/" title="XooNIps" target="_blank" rel="noopener noreferrer">
<img src={imageBannerXoonips} alt="XooNIps" />
</a>
</span>
</div>
<div className="copyright">
<span>Copyright (C) 2018 Neuroinformatics Unit, RIKEN Center for Brain Science</span>
</div>
</footer>
);
};
export default Footer;
+73 -65
View File
@@ -1,73 +1,81 @@
import React from "react";
import { Link } from "react-router-dom";
import LangFlag from "../common/lib/LangFlag";
import Config, { MultiLang } from "../config";
import imageJnode from "./assets/images/jnode.png";
import type React from 'react';
import { Link } from 'react-router-dom';
import LangFlag from '../common/lib/LangFlag';
import Config, { type MultiLang } from '../config';
import imageJnode from './assets/images/jnode.png';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const Header: React.FC<Props> = (props: Props) => {
return (
<header id="header">
<div className="logo">
<Link to="/" title="Home">
<span className="hidden">{Config.SITE_TITLE}</span>
</Link>
const Header: React.FC<Props> = (_props: Props) => {
return (
<header id="header">
<div className="logo">
<Link to="/" title="Home">
<span className="hidden">{Config.SITE_TITLE}</span>
</Link>
</div>
<div className="jnode">
<a href="https://www.neuroinf.jp/" target="_blank" rel="noopener noreferrer" title="INCF Japan Node">
<img src={imageJnode} alt="INCF Japan Node" />
</a>
</div>
<nav className="menubar">
<ul className="mainmenu">
<li>
<Link
id="hmm01"
to="/modules/nimgdocs/index.php?docname=about_NIMGPF"
title="NIMG-PFの基本理念、委員会構成、取り扱い説明等を掲載しています。"
>
NIMG-PFについて About This Site
</Link>
</li>
<li>
<Link id="hmm02" to="/modules/xoonips/" title="データベースを検索することができます。">
XooNIpsモード XooNIps database mode
</Link>
</li>
<li>
<Link id="hmm03" to="/modules/nimgsearch/" title="脳図から関連する文献を検索することができます。">
脳図検索モード search with Brain Atlas
</Link>
</li>
<li>
<Link
id="hmm04"
to="/modules/nimgdocs/tutorials/"
title="脳イメージングについて説明した教育資料を閲覧できます。"
>
チュートリアル NIMG-PF Tutorials
</Link>
</li>
<li>
<Link id="hsm01" to="/modules/xnpnimgcenter/utilities/" title="ユーティリティ">
ユーティリティ Utilities
</Link>
</li>
<li>
<Link id="hsm03" to="/modules/nimgdocs/index.php?docname=3DBrowse" title="日本人脳データ">
日本人脳データ 3D Browse
</Link>
</li>
<li>
<Link id="hsm04" to="/modules/nimgdocs/index.php?docname=3DBrowse" title="日本ザル脳データ">
日本ザル脳データ 3D Browse
</Link>
</li>
<li>
<div className="lang">
<LangFlag lang="en" />
<LangFlag lang="ja" />
</div>
<div className="jnode">
<a href="https://www.neuroinf.jp/" target="_blank" rel="noopener noreferrer" title="INCF Japan Node">
<img src={imageJnode} alt="INCF Japan Node" />
</a>
</div>
<nav className="menubar">
<ul className="mainmenu">
<li>
<Link id="hmm01" to="/modules/nimgdocs/index.php?docname=about_NIMGPF" title="NIMG-PFの基本理念、委員会構成、取り扱い説明等を掲載しています。">
NIMG-PFについて About This Site
</Link>
</li>
<li>
<Link id="hmm02" to="/modules/xoonips/" title="データベースを検索することができます。">
XooNIpsモード XooNIps database mode
</Link>
</li>
<li>
<Link id="hmm03" to="/modules/nimgsearch/" title="脳図から関連する文献を検索することができます。">
脳図検索モード search with Brain Atlas
</Link>
</li>
<li>
<Link id="hmm04" to="/modules/nimgdocs/tutorials/" title="脳イメージングについて説明した教育資料を閲覧できます。">
チュートリアル NIMG-PF Tutorials
</Link>
</li>
<li>
<Link id="hsm01" to="/modules/xnpnimgcenter/utilities/" title="ユーティリティ">
ユーティリティ Utilities
</Link>
</li>
<li>
<Link id="hsm03" to="/modules/nimgdocs/index.php?docname=3DBrowse" title="日本人脳データ">
日本人脳データ 3D Browse
</Link>
</li>
<li>
<Link id="hsm04" to="/modules/nimgdocs/index.php?docname=3DBrowse" title="日本ザル脳データ">
日本ザル脳データ 3D Browse
</Link>
</li>
<li>
<div className="lang">
<LangFlag lang="en" />
<LangFlag lang="ja" />
</div>
</li>
</ul>
</nav>
</header>
);
</li>
</ul>
</nav>
</header>
);
};
export default Header;
+123 -123
View File
@@ -1,148 +1,148 @@
import React from "react";
import { Outlet, Route, Routes } from "react-router-dom";
import BrainExplorerIndex from "../brainexplorer/BrainExplorer";
import XoopsPathRedirect from "../common/XoopsPathRedirect";
import { MultiLang } from "../config";
import CreditsMenu from "../credits/blocks/CreditsMenu";
import Credits from "../credits/Credits";
import NimgcenterUtilities from "../nimgcenter/NimgenterUtilities";
import Nimgdocs from "../nimgdocs/Nimgdocs";
import Nimgsearch from "../nimgsearch/Nimgsearch";
import IndexTree from "../xoonips/blocks/IndexTree";
import Ranking from "../xoonips/blocks/Rankings";
import RecentContents from "../xoonips/blocks/RecentContents";
import Search2 from "../xoonips/blocks/Search";
import Xoonips from "../xoonips/Xoonips";
import BulletinBoard from "./blocks/BulletinBoard";
import HowToUseLinks from "./blocks/HowToUseLinks";
import HowToUseVideo from "./blocks/HowToUseVideo";
import Information from "./blocks/Information";
import Questionnaire from "./blocks/Questionnaire";
import RecentStatus from "./blocks/RecentStatus";
import Footer from "./Footer";
import Header from "./Header";
import type React from 'react';
import { Outlet, Route, Routes } from 'react-router-dom';
import BrainExplorerIndex from '../brainexplorer/BrainExplorer';
import XoopsPathRedirect from '../common/XoopsPathRedirect';
import type { MultiLang } from '../config';
import CreditsMenu from '../credits/blocks/CreditsMenu';
import Credits from '../credits/Credits';
import NimgcenterUtilities from '../nimgcenter/NimgenterUtilities';
import Nimgdocs from '../nimgdocs/Nimgdocs';
import Nimgsearch from '../nimgsearch/Nimgsearch';
import IndexTree from '../xoonips/blocks/IndexTree';
import Ranking from '../xoonips/blocks/Rankings';
import RecentContents from '../xoonips/blocks/RecentContents';
import Search2 from '../xoonips/blocks/Search';
import Xoonips from '../xoonips/Xoonips';
import BulletinBoard from './blocks/BulletinBoard';
import HowToUseLinks from './blocks/HowToUseLinks';
import HowToUseVideo from './blocks/HowToUseVideo';
import Information from './blocks/Information';
import Questionnaire from './blocks/Questionnaire';
import RecentStatus from './blocks/RecentStatus';
import Footer from './Footer';
import Header from './Header';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const MainContent: React.FC<Props> = (props: Props) => {
return (
<div id="main">
<div className="mainContent">
<Outlet />
</div>
</div>
);
const MainContent: React.FC<Props> = (_props: Props) => {
return (
<div id="main">
<div className="mainContent">
<Outlet />
</div>
</div>
);
};
const CenterColumn: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div id="centercolumn">
<Outlet />
<Routes>
<Route path="/*" element={<MainContent lang={lang} />}>
<Route path="modules/credits/*" element={<Credits lang={lang} />} />
<Route path="modules/xoonips/*" element={<Xoonips lang={lang} />} />
<Route path="modules/nimgdocs/*" element={<Nimgdocs lang={lang} />} />
<Route path="modules/nimgsearch/*" element={<Nimgsearch lang={lang} />} />
<Route path="modules/xnpnimgcenter/utilities/*" element={<NimgcenterUtilities lang={lang} />} />
<Route path="modules/brainexplorer/*" element={<BrainExplorerIndex lang={lang} />} />
<Route path="*" element={<XoopsPathRedirect lang={lang} />} />
</Route>
</Routes>
</div>
);
const { lang } = props;
return (
<div id="centercolumn">
<Outlet />
<Routes>
<Route path="/*" element={<MainContent lang={lang} />}>
<Route path="modules/credits/*" element={<Credits lang={lang} />} />
<Route path="modules/xoonips/*" element={<Xoonips lang={lang} />} />
<Route path="modules/nimgdocs/*" element={<Nimgdocs lang={lang} />} />
<Route path="modules/nimgsearch/*" element={<Nimgsearch lang={lang} />} />
<Route path="modules/xnpnimgcenter/utilities/*" element={<NimgcenterUtilities lang={lang} />} />
<Route path="modules/brainexplorer/*" element={<BrainExplorerIndex lang={lang} />} />
<Route path="*" element={<XoopsPathRedirect lang={lang} />} />
</Route>
</Routes>
</div>
);
};
const CenterColumnBlock: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<div id="centerCcolumn">
<Information lang={lang} />
<BulletinBoard lang={lang} />
<HowToUseVideo lang={lang} />
<HowToUseLinks lang={lang} />
<Questionnaire lang={lang} />
<RecentStatus lang={lang} />
const { lang } = props;
return (
<>
<div id="centerCcolumn">
<Information lang={lang} />
<BulletinBoard lang={lang} />
<HowToUseVideo lang={lang} />
<HowToUseLinks lang={lang} />
<Questionnaire lang={lang} />
<RecentStatus lang={lang} />
</div>
<div className="col2">
<div id="centerLcolumn">
<div className="block">
<div className="blockTitle">XooNIps Ranking</div>
<div className="blockContent">
<Ranking lang={lang} />
</div>
<div className="col2">
<div id="centerLcolumn">
<div className="block">
<div className="blockTitle">XooNIps Ranking</div>
<div className="blockContent">
<Ranking lang={lang} />
</div>
</div>
</div>
<div id="centerRcolumn">
<div className="block">
<div className="blockTitle">XooNIps Update</div>
<div className="blockContent">
<RecentContents lang={lang} />
</div>
</div>
</div>
</div>
</div>
<div id="centerRcolumn">
<div className="block">
<div className="blockTitle">XooNIps Update</div>
<div className="blockContent">
<RecentContents lang={lang} />
</div>
</>
);
</div>
</div>
</div>
</>
);
};
const LeftColumn: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div id="leftcolumn">
<Outlet />
<div className="block">
<div className="blockTitle">Index Tree</div>
<div className="blockContent">
<IndexTree lang={lang} />
</div>
</div>
<div className="block">
<div className="blockTitle">Site Information</div>
<div className="blockContent">
<CreditsMenu lang={lang} />
</div>
</div>
const { lang } = props;
return (
<div id="leftcolumn">
<Outlet />
<div className="block">
<div className="blockTitle">Index Tree</div>
<div className="blockContent">
<IndexTree lang={lang} />
</div>
);
</div>
<div className="block">
<div className="blockTitle">Site Information</div>
<div className="blockContent">
<CreditsMenu lang={lang} />
</div>
</div>
</div>
);
};
const XoopsSearchBlock: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div className="block">
<div className="blockTitle">Item Search</div>
<div className="blockContent">
<Search2 lang={lang} />
</div>
</div>
);
const { lang } = props;
return (
<div className="block">
<div className="blockTitle">Item Search</div>
<div className="blockContent">
<Search2 lang={lang} />
</div>
</div>
);
};
const Page: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div id="page">
<Header lang={lang} />
<div className="col2">
<Routes>
<Route path="/*" element={<CenterColumn lang={lang} />}>
<Route index element={<CenterColumnBlock lang={lang} />} />
</Route>
</Routes>
<Routes>
<Route path="/*" element={<LeftColumn lang={lang} />}>
<Route path="modules/xoonips/*" element={<XoopsSearchBlock lang={lang} />} />
</Route>
</Routes>
</div>
<Footer lang={lang} />
</div>
);
const { lang } = props;
return (
<div id="page">
<Header lang={lang} />
<div className="col2">
<Routes>
<Route path="/*" element={<CenterColumn lang={lang} />}>
<Route index element={<CenterColumnBlock lang={lang} />} />
</Route>
</Routes>
<Routes>
<Route path="/*" element={<LeftColumn lang={lang} />}>
<Route path="modules/xoonips/*" element={<XoopsSearchBlock lang={lang} />} />
</Route>
</Routes>
</div>
<Footer lang={lang} />
</div>
);
};
export default Page;
+6 -5
View File
@@ -354,7 +354,8 @@ tr.odd > td,
height: 30px;
padding: 8px 10px 0 28px;
border-radius: 3px 3px 3px 3px;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5),
box-shadow:
2px 2px 2px rgba(0, 0, 0, 0.5),
0 0 10px rgba(255, 255, 255, 0.5) inset;
color: #fdfdfd;
background: url(images/blockTitleIndent16.png) no-repeat 8px center #ef6e00;
@@ -387,10 +388,10 @@ tr.odd > td,
}
#main h1 {
background:url(images/h1_indent.png) no-repeat left center;
padding: 5px 0 5px 33px;
background: url(images/h1_indent.png) no-repeat left center;
padding: 5px 0 5px 33px;
}
#main h2 {
background:url(images/h2_background.png);
background: url(images/h2_background.png);
padding: 5px;
}
}
+19 -15
View File
@@ -1,23 +1,27 @@
import React from "react";
import { Link } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import { Link } from 'react-router-dom';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const BulletinBoard: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div className="block">
<div className="blockTitle">{Functions.mlang("[en]Bulletin Board[/en][ja]掲示板[/ja]", lang)}</div>
<div className="blockContent">
<p style={{ fontWeight: "bold", textAlign: "center" }}>{Functions.mlang("[en]There is no post yet.[/en][ja]まだ投稿がありません[/ja]", lang)}</p>
<Link to="/modules/nimgdocs/bulletin/log.php">{Functions.mlang("[en]View expired posts[/en][ja]掲示期間が終了した投稿を見る[/ja]", lang)}</Link>
</div>
</div>
);
const { lang } = props;
return (
<div className="block">
<div className="blockTitle">{Functions.mlang('[en]Bulletin Board[/en][ja]掲示板[/ja]', lang)}</div>
<div className="blockContent">
<p style={{ fontWeight: 'bold', textAlign: 'center' }}>
{Functions.mlang('[en]There is no post yet.[/en][ja]まだ投稿がありません[/ja]', lang)}
</p>
<Link to="/modules/nimgdocs/bulletin/log.php">
{Functions.mlang('[en]View expired posts[/en][ja]掲示期間が終了した投稿を見る[/ja]', lang)}
</Link>
</div>
</div>
);
};
export default BulletinBoard;
+34 -32
View File
@@ -1,40 +1,42 @@
import React from "react";
import { Link } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import { Link } from 'react-router-dom';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const HowToUseLinks: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]How to use NIMG-PF[/en][ja]NIMG-PFの使い方[/ja]";
const content = {
en: (
<ul>
<li>
<a href="/modules/nimgdocs/docs/pdf/NIMGPFmanual_en.pdf">NeuroImaging-Plarform (NIMG-PF) User Manual</a>
</li>
</ul>
),
ja: (
<ul>
<li>
<a href="/modules/nimgdocs/docs/pdf/NIMGPFmanual_ja.pdf">ニューロイメージング・プラットフォーム(NIMG-PF)の利用マニュアル</a>
</li>
<li>
<Link to="/modules/nimgdocs/index.php?docname=introduction">NIMG-PFの機能と使い方 解説一覧表</Link>
</li>
</ul>
),
};
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">{content[lang]}</div>
</div>
);
const { lang } = props;
const title = '[en]How to use NIMG-PF[/en][ja]NIMG-PFの使い方[/ja]';
const content = {
en: (
<ul>
<li>
<a href="/modules/nimgdocs/docs/pdf/NIMGPFmanual_en.pdf">NeuroImaging-Plarform (NIMG-PF) User Manual</a>
</li>
</ul>
),
ja: (
<ul>
<li>
<a href="/modules/nimgdocs/docs/pdf/NIMGPFmanual_ja.pdf">
ニューロイメージング・プラットフォーム(NIMG-PF)の利用マニュアル
</a>
</li>
<li>
<Link to="/modules/nimgdocs/index.php?docname=introduction">NIMG-PFの機能と使い方 解説一覧表</Link>
</li>
</ul>
),
};
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">{content[lang]}</div>
</div>
);
};
export default HowToUseLinks;
+56 -53
View File
@@ -1,66 +1,69 @@
import React, { useState } from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import { useState } from 'react';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const HowToUseVideo: React.FC<Props> = (props) => {
const { lang } = props;
const [skip, setSkip] = useState<boolean>(false);
const { lang } = props;
const [skip, setSkip] = useState<boolean>(false);
const onClickShowMovie = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
e.preventDefault();
setSkip(false);
};
const onClickShowMovie = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
e.preventDefault();
setSkip(false);
};
const onClickSkipMovie = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
e.preventDefault();
setSkip(true);
};
const onClickSkipMovie = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
e.preventDefault();
setSkip(true);
};
const url = "/modules/nimgdocs/docs/viewlet/";
const name = "ty-02" + (lang === "en" ? "eng" : "jap");
const preview = `${url}/${name}_small.png`;
const poster = `${url}/${name}.png`;
const movieWebm = `${url}/${name}.webm`;
const movieMp4 = `${url}/${name}.mp4`;
const url = '/modules/nimgdocs/docs/viewlet/';
const name = 'ty-02' + (lang === 'en' ? 'eng' : 'jap');
const preview = `${url}/${name}_small.png`;
const poster = `${url}/${name}.png`;
const movieWebm = `${url}/${name}.webm`;
const movieMp4 = `${url}/${name}.mp4`;
return (
<div className="block">
<div className="blockContent">
<div style={{ textAlign: "center" }}>
<div style={{ fontWeight: "bold", fontSize: "15pt", margin: "0 0 10px 0" }}>{Functions.mlang("[en]Function and how to use NIMG-PF[/en][ja]NIMG-PFの機能と使い方[/ja]", lang)}</div>
{skip ? (
<>
<div>
<a href="/" onClick={onClickShowMovie}>
Show Movie
</a>
</div>
<img src={preview} alt="How to Use" />
</>
) : (
<>
<div>
<a href="/" onClick={onClickSkipMovie}>
Skip Movie
</a>
</div>
<video poster={poster} autoPlay loop>
<source type="video/mp4" src={movieMp4} />
<source type="video/webm" src={movieWebm} />
<p>
Your browser doesn't support HTML5 video. Here is a <a href={movieMp4}>link to the video</a> instead.
</p>
</video>
</>
)}
</div>
</div>
return (
<div className="block">
<div className="blockContent">
<div style={{ textAlign: 'center' }}>
<div style={{ fontWeight: 'bold', fontSize: '15pt', margin: '0 0 10px 0' }}>
{Functions.mlang('[en]Function and how to use NIMG-PF[/en][ja]NIMG-PFの機能と使い方[/ja]', lang)}
</div>
{skip ? (
<>
<div>
<a href="/" onClick={onClickShowMovie}>
Show Movie
</a>
</div>
<img src={preview} alt="How to Use" />
</>
) : (
<>
<div>
<a href="/" onClick={onClickSkipMovie}>
Skip Movie
</a>
</div>
<video poster={poster} autoPlay loop>
<source type="video/mp4" src={movieMp4} />
<source type="video/webm" src={movieWebm} />
<p>
Your browser doesn't support HTML5 video. Here is a <a href={movieMp4}>link to the video</a> instead.
</p>
</video>
</>
)}
</div>
);
</div>
</div>
);
};
export default HowToUseVideo;
+21 -21
View File
@@ -1,29 +1,29 @@
import React from "react";
import NoticeSiteHasBeenArchived from "../../common/lib/NoticeSiteHasBeenArchived";
import XoopsCode from "../../common/lib/XoopsCode";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import NoticeSiteHasBeenArchived from '../../common/lib/NoticeSiteHasBeenArchived';
import XoopsCode from '../../common/lib/XoopsCode';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const Information: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]Information[/en][ja]お知らせ[/ja]";
const content = {
en: "This site was opened to the public at 23 Jun 2009. <s>When you submit an application form of user registration, please write it correctly. The application may not be approved, if it includes incorrect information or only abbreviation for address, organization, and division name.</s> (Currently, user registration is closed.)",
ja: "このサイトは2009年6月23日に一般公開され、<s>一般ユーザの登録を受け付けています。ユーザ登録されると、データ登録やダウンロードが可能になるなど、より多くの機能をご利用出来ます。ユーザ登録の際には、正確な情報を入力していただくようお願いします。不正確な情報が記入されていたり、住所・機関・所属などが短縮形だけであるなどの場合は、登録が承認されないことがあります。</s> (現在、新規登録を受け付けておりません。)",
};
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">
<NoticeSiteHasBeenArchived lang={lang} />
<XoopsCode lang={lang} text={content[lang]} dohtml={true} />
</div>
</div>
);
const { lang } = props;
const title = '[en]Information[/en][ja]お知らせ[/ja]';
const content = {
en: 'This site was opened to the public at 23 Jun 2009. <s>When you submit an application form of user registration, please write it correctly. The application may not be approved, if it includes incorrect information or only abbreviation for address, organization, and division name.</s> (Currently, user registration is closed.)',
ja: 'このサイトは2009年6月23日に一般公開され、<s>一般ユーザの登録を受け付けています。ユーザ登録されると、データ登録やダウンロードが可能になるなど、より多くの機能をご利用出来ます。ユーザ登録の際には、正確な情報を入力していただくようお願いします。不正確な情報が記入されていたり、住所・機関・所属などが短縮形だけであるなどの場合は、登録が承認されないことがあります。</s> (現在、新規登録を受け付けておりません。)',
};
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">
<NoticeSiteHasBeenArchived lang={lang} />
<XoopsCode lang={lang} text={content[lang]} dohtml={true} />
</div>
</div>
);
};
export default Information;
+32 -22
View File
@@ -1,31 +1,41 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const Questionnaire: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]Questionnaire and Contact address[/en][ja]NIMG-PFの使い方[/ja]";
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">
<div>
{Functions.mlang("[en]Please fill out the questionnaire.[/en][ja]アンケートにご協力ください[/ja]", lang)} ⇒{" "}
<a href="https://spreadsheets.google.com/viewform?formkey=cEpsb3doVHlOTUJ0UHcxQ3k1TFNHSlE6MA" target="_blank" rel="noopener noreferrer">
{Functions.mlang("[en]Go to the page of questionnaire.[/en][ja]アンケートのページへ[/ja]", lang)}
</a>
</div>
<div>{Functions.mlang("[en]Our e-mail address is as follows. If you have any questions, please let me know.[/en][ja]NIMG-PFについてご質問等ありましたら、以下までお願いします。[/ja]", lang)}</div>
<p style={{ textAlign: "center", textDecoration: "underline" }}>
{Functions.mlang("[en]Contact information of NIMG-PF[/en][ja]NIMG-PFについてのご連絡先[/ja]", lang)}: <a href="mailto:NIMG-info@ml.nict.go.jp">NIMG-info@ml.nict.go.jp</a>
</p>
</div>
const { lang } = props;
const title = '[en]Questionnaire and Contact address[/en][ja]NIMG-PFの使い方[/ja]';
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">
<div>
{Functions.mlang('[en]Please fill out the questionnaire.[/en][ja]アンケートにご協力ください[/ja]', lang)} ⇒{' '}
<a
href="https://spreadsheets.google.com/viewform?formkey=cEpsb3doVHlOTUJ0UHcxQ3k1TFNHSlE6MA"
target="_blank"
rel="noopener noreferrer"
>
{Functions.mlang('[en]Go to the page of questionnaire.[/en][ja]アンケートのページへ[/ja]', lang)}
</a>
</div>
);
<div>
{Functions.mlang(
'[en]Our e-mail address is as follows. If you have any questions, please let me know.[/en][ja]NIMG-PFについてご質問等ありましたら、以下までお願いします。[/ja]',
lang,
)}
</div>
<p style={{ textAlign: 'center', textDecoration: 'underline' }}>
{Functions.mlang('[en]Contact information of NIMG-PF[/en][ja]NIMG-PFについてのご連絡先[/ja]', lang)}:{' '}
<a href="mailto:NIMG-info@ml.nict.go.jp">NIMG-info@ml.nict.go.jp</a>
</p>
</div>
</div>
);
};
export default Questionnaire;
+12 -12
View File
@@ -1,20 +1,20 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import type React from 'react';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const RecentStatus: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]NIMG-PF recent status[/en][ja]NIMG-PF更新情報[/ja]";
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent"></div>
</div>
);
const { lang } = props;
const title = '[en]NIMG-PF recent status[/en][ja]NIMG-PF更新情報[/ja]';
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent"></div>
</div>
);
};
export default RecentStatus;
+99 -92
View File
@@ -1,136 +1,143 @@
import moment from "moment";
import XRegExp from "xregexp";
import Config, { MultiLang } from "./config";
import moment from 'moment';
import XRegExp from 'xregexp';
import Config, { type MultiLang } from './config';
const escape = (str: string): string => {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
return "%" + c.charCodeAt(0).toString(16);
});
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
return '%' + c.charCodeAt(0).toString(16);
});
};
const unescape = (str: string): string => {
return decodeURIComponent(str);
return decodeURIComponent(str);
};
const htmlspecialchars = (str: string): string => {
return str.replace(/(<|>|&|'|")/g, (match) => {
switch (match) {
case "<":
return "&lt;";
case ">":
return "&gt;";
case "&":
return "&amp;";
case "'":
return "&#039;";
case '"':
return "&quot;";
}
return "";
});
return str.replace(/(<|>|&|'|")/g, (match) => {
switch (match) {
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
case "'":
return '&#039;';
case '"':
return '&quot;';
}
return '';
});
};
const camelCase = (str: string): string => {
return str.replace(/[-_](.)/g, (...matches) => {
return matches[1].toUpperCase();
});
return str.replace(/[-_](.)/g, (...matches) => {
return matches[1].toUpperCase();
});
};
const snakeCase = (str: string): string => {
var camel = camelCase(str);
return camel.replace(/[A-Z]/g, (...matches) => {
return "_" + matches[0].charAt(0).toLowerCase();
});
const camel = camelCase(str);
return camel.replace(/[A-Z]/g, (...matches) => {
return '_' + matches[0].charAt(0).toLowerCase();
});
};
const pascalCase = (str: string): string => {
var camel = camelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
const camel = camelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
};
const base64Encode = (str: string): string => {
return btoa(unescape(encodeURIComponent(str)));
return btoa(unescape(encodeURIComponent(str)));
};
const base64Decode = (str: string): string => {
return decodeURIComponent(escape(atob(str)));
return decodeURIComponent(escape(atob(str)));
};
const formatDate = (timestamp: number, format: string): string => {
return moment(new Date(timestamp * 1000)).format(format);
return moment(new Date(timestamp * 1000)).format(format);
};
const ordinal = (num: number): string => {
const hundred = num % 100;
if (hundred >= 10 && hundred <= 20) {
return "th";
}
const ten = num % 10;
const suffix = ["st", "nd", "rd"];
return ten > 0 && ten < 4 ? suffix[ten - 1] : "th";
const hundred = num % 100;
if (hundred >= 10 && hundred <= 20) {
return 'th';
}
const ten = num % 10;
const suffix = ['st', 'nd', 'rd'];
return ten > 0 && ten < 4 ? suffix[ten - 1] : 'th';
};
const mlang = (str: string, lang: MultiLang): string => {
const mlLangs = ["en", "ja"];
// escape brackets inside of <input type="text" value="...">
let text = str.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/\[/g, "__ml[ml__") + m3;
}
return whole;
});
// escape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (whole, m1, m2, m3) => {
return m1 + m2.replace(/\[/g, "__ml[ml__") + m3;
});
// simple pattern to strip selected lang_tags
const re = new RegExp("\\[/?(?:[^\\]]+\\|)?" + lang + "(?:\\|[^\\]]+)?\\](?:<br />)?", "g");
text = text.replace(re, "");
// eliminate description between the other language tags.
mlLangs.forEach((mlLang) => {
if (mlLang !== lang) {
const re = XRegExp("\\[(?:[^/][^\\]]+\\|)?" + mlLang + "(?:\\|[^\\]]+)?\\].*?\\[/(?:[^\\]]+\\|)?" + mlLang + "(?:\\|[^\\]]+)?\\](?:<br />)?", "isg");
text = text.replace(re, (whole) => {
return whole.match(/<\/table>/) ? whole : "";
});
}
});
// unescape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (whole, m1, m2, m3) => {
return m1 + m2.replace(/__ml\[ml__/g, "[") + m3;
});
// unescape brackets inside of <input type="text" value="...">
text = text.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/__ml\[ml__/g, "[") + m3;
}
return whole;
});
return text;
const mlLangs = ['en', 'ja'];
// escape brackets inside of <input type="text" value="...">
let text = str.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/\[/g, '__ml[ml__') + m3;
}
return whole;
});
// escape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (_whole, m1, m2, m3) => {
return m1 + m2.replace(/\[/g, '__ml[ml__') + m3;
});
// simple pattern to strip selected lang_tags
const re = new RegExp('\\[/?(?:[^\\]]+\\|)?' + lang + '(?:\\|[^\\]]+)?\\](?:<br />)?', 'g');
text = text.replace(re, '');
// eliminate description between the other language tags.
mlLangs.forEach((mlLang) => {
if (mlLang !== lang) {
const re = XRegExp(
'\\[(?:[^/][^\\]]+\\|)?' +
mlLang +
'(?:\\|[^\\]]+)?\\].*?\\[/(?:[^\\]]+\\|)?' +
mlLang +
'(?:\\|[^\\]]+)?\\](?:<br />)?',
'isg',
);
text = text.replace(re, (whole) => {
return whole.match(/<\/table>/) ? whole : '';
});
}
});
// unescape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (_whole, m1, m2, m3) => {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
});
// unescape brackets inside of <input type="text" value="...">
text = text.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
}
return whole;
});
return text;
};
const siteTitle = (lang: MultiLang): string => {
return mlang(Config.SITE_TITLE, lang);
return mlang(Config.SITE_TITLE, lang);
};
const siteSlogan = (lang: MultiLang): string => {
return mlang(Config.SITE_SLOGAN, lang);
return mlang(Config.SITE_SLOGAN, lang);
};
const Functions = {
escape,
unescape,
htmlspecialchars,
camelCase,
snakeCase,
pascalCase,
base64Encode,
base64Decode,
formatDate,
ordinal,
mlang,
siteTitle,
siteSlogan,
escape,
unescape,
htmlspecialchars,
camelCase,
snakeCase,
pascalCase,
base64Encode,
base64Decode,
formatDate,
ordinal,
mlang,
siteTitle,
siteSlogan,
};
export default Functions;
-18
View File
@@ -1,18 +0,0 @@
import React from "react";
import "react-app-polyfill/stable";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<App />
</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();
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
+132 -123
View File
@@ -1,142 +1,151 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { MultiLang } from "../config";
import BrainCoordinateUtil, { BrainCoordinate, ConversionType } from "./lib/BrainCoordinateUtil";
import type React from 'react';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import type { MultiLang } from '../config';
import BrainCoordinateUtil, { type BrainCoordinate, type ConversionType } from './lib/BrainCoordinateUtil';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const NimgcenterBrainConv: React.FC<Props> = (props: Props) => {
const defaultInputFormat = "\\d+\\.?\\d*";
const defaultOutputFormat = "%x,%y,%z\\n";
const NimgcenterBrainConv: React.FC<Props> = (_props: Props) => {
const defaultInputFormat = '\\d+\\.?\\d*';
const defaultOutputFormat = '%x,%y,%z\\n';
const [inputFormat, setInputFormat] = useState<string>(defaultInputFormat);
const [inputArea, setInputArea] = useState<string>("");
const [outputFormat, setOutputFormat] = useState<string>(defaultOutputFormat);
const [outputArea, setOutputArea] = useState<string>("");
const [conversionType, setConversionType] = useState<ConversionType>("icbm_spm2tal");
const [inputFormat, setInputFormat] = useState<string>(defaultInputFormat);
const [inputArea, setInputArea] = useState<string>('');
const [outputFormat, setOutputFormat] = useState<string>(defaultOutputFormat);
const [outputArea, setOutputArea] = useState<string>('');
const [conversionType, setConversionType] = useState<ConversionType>('icbm_spm2tal');
const parseInput = (input: string, format: string): BrainCoordinate[] => {
const ret: BrainCoordinate[] = [];
const regObj = new RegExp(format, "g");
const values = input.match(regObj) || [];
for (let i = 0; i + 2 < values.length; i += 3) {
ret.push({ x: parseFloat(values[i]), y: parseFloat(values[i + 1]), z: parseFloat(values[i + 2]) });
}
return ret;
};
const parseInput = (input: string, format: string): BrainCoordinate[] => {
const ret: BrainCoordinate[] = [];
const regObj = new RegExp(format, 'g');
const values = input.match(regObj) || [];
for (let i = 0; i + 2 < values.length; i += 3) {
ret.push({ x: parseFloat(values[i]), y: parseFloat(values[i + 1]), z: parseFloat(values[i + 2]) });
}
return ret;
};
const formatNumber = (value: number): string => {
const digit = 4;
const width = 7;
const ret = value.toFixed(digit);
return ret.length < width ? " ".repeat(width - ret.length) + ret : ret;
};
const formatNumber = (value: number): string => {
const digit = 4;
const width = 7;
const ret = value.toFixed(digit);
return ret.length < width ? ' '.repeat(width - ret.length) + ret : ret;
};
const onChangeInputFormat = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputFormat(event.target.value);
};
const onChangeInputFormat = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputFormat(event.target.value);
};
const onClickDefaultInputFormatButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setInputFormat(defaultInputFormat);
};
const onClickDefaultInputFormatButton = (_event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setInputFormat(defaultInputFormat);
};
const onChangeInputArea = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setInputArea(event.target.value);
};
const onChangeInputArea = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setInputArea(event.target.value);
};
const onChangeOutputFormat = (event: React.ChangeEvent<HTMLInputElement>) => {
setOutputFormat(event.target.value);
};
const onChangeOutputFormat = (event: React.ChangeEvent<HTMLInputElement>) => {
setOutputFormat(event.target.value);
};
const onClickDefaultOutputFormatButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setOutputFormat(defaultOutputFormat);
};
const onClickDefaultOutputFormatButton = (_event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setOutputFormat(defaultOutputFormat);
};
const onChangeMatrixType = (event: React.ChangeEvent<HTMLSelectElement>) => {
if (BrainCoordinateUtil.isConversionType(event.target.value)) {
setConversionType(event.target.value as ConversionType);
}
};
const onChangeMatrixType = (event: React.ChangeEvent<HTMLSelectElement>) => {
if (BrainCoordinateUtil.isConversionType(event.target.value)) {
setConversionType(event.target.value as ConversionType);
}
};
const onClickClearButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setInputArea("");
setOutputArea("");
};
const onClickClearButton = (_event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setInputArea('');
setOutputArea('');
};
const onClickConvertButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
const inputValues = parseInput(inputArea, inputFormat);
const outputValues = inputValues.map((inputValue) => BrainCoordinateUtil.convertCoordinate(conversionType, inputValue));
const outputTextArea = outputValues
.map((value) => {
return outputFormat.replace("%x", formatNumber(value.x)).replace("%y", formatNumber(value.y)).replace("%z", formatNumber(value.z)).replace("\\n", "\n");
})
.join("");
setOutputArea(outputTextArea);
};
const matrix = BrainCoordinateUtil.getConversionMatrix(conversionType);
const matrixArea = "Conversion Matrix:\n" + matrix.map((vect) => " " + vect.map((value) => formatNumber(value)).join(", ")).join("\n");
return (
<div style={{ textAlign: "center" }}>
<div style={{ background: "navajowhite", padding: "3px 0" }}>
Brain Coordinate Converter is able to convert list of brain coordinate. <Link to="./howtouse.php">How To Use</Link>
</div>
<table style={{ textAlign: "left" }}>
<tbody>
<tr>
<td>
<b>Input format: (Regular expression).</b>
<br />
<input type="text" value={inputFormat} onChange={onChangeInputFormat} />
<input type="button" onClick={onClickDefaultInputFormatButton} value="Default" />
</td>
<td>
<b>Output format: (%x,%y,%z is replaced. and \n)</b>
<br />
<input type="text" value={outputFormat} onChange={onChangeOutputFormat} />
<input type="button" onClick={onClickDefaultOutputFormatButton} value="Default" />
</td>
</tr>
<tr>
<td>
<b>Input:</b>
<br />
<textarea cols={37} rows={10} wrap="off" value={inputArea} onChange={onChangeInputArea} />
</td>
<td>
<b>Results:</b>
<br />
<textarea cols={37} rows={10} wrap="off" value={outputArea} readOnly />
</td>
</tr>
<tr>
<td>
<select size={8} onChange={onChangeMatrixType} value={conversionType}>
{BrainCoordinateUtil.getConversionTypes().map((type) => (
<option key={type} value={type}>
{BrainCoordinateUtil.getConversionLabel(type)}
</option>
))}
</select>
</td>
<td>
<textarea cols={37} rows={5} wrap="off" value={matrixArea} readOnly />
</td>
</tr>
<tr>
<td>
<input type="button" onClick={onClickConvertButton} value="Convert" />
</td>
<td>
<input type="button" onClick={onClickClearButton} value="Clear" />
</td>
</tr>
</tbody>
</table>
</div>
const onClickConvertButton = (_event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
const inputValues = parseInput(inputArea, inputFormat);
const outputValues = inputValues.map((inputValue) =>
BrainCoordinateUtil.convertCoordinate(conversionType, inputValue),
);
const outputTextArea = outputValues
.map((value) => {
return outputFormat
.replace('%x', formatNumber(value.x))
.replace('%y', formatNumber(value.y))
.replace('%z', formatNumber(value.z))
.replace('\\n', '\n');
})
.join('');
setOutputArea(outputTextArea);
};
const matrix = BrainCoordinateUtil.getConversionMatrix(conversionType);
const matrixArea =
'Conversion Matrix:\n' + matrix.map((vect) => ' ' + vect.map((value) => formatNumber(value)).join(', ')).join('\n');
return (
<div style={{ textAlign: 'center' }}>
<div style={{ background: 'navajowhite', padding: '3px 0' }}>
Brain Coordinate Converter is able to convert list of brain coordinate.{' '}
<Link to="./howtouse.php">How To Use</Link>
</div>
<table style={{ textAlign: 'left' }}>
<tbody>
<tr>
<td>
<b>Input format: (Regular expression).</b>
<br />
<input type="text" value={inputFormat} onChange={onChangeInputFormat} />
<input type="button" onClick={onClickDefaultInputFormatButton} value="Default" />
</td>
<td>
<b>Output format: (%x,%y,%z is replaced. and \n)</b>
<br />
<input type="text" value={outputFormat} onChange={onChangeOutputFormat} />
<input type="button" onClick={onClickDefaultOutputFormatButton} value="Default" />
</td>
</tr>
<tr>
<td>
<b>Input:</b>
<br />
<textarea cols={37} rows={10} wrap="off" value={inputArea} onChange={onChangeInputArea} />
</td>
<td>
<b>Results:</b>
<br />
<textarea cols={37} rows={10} wrap="off" value={outputArea} readOnly />
</td>
</tr>
<tr>
<td>
<select size={8} onChange={onChangeMatrixType} value={conversionType}>
{BrainCoordinateUtil.getConversionTypes().map((type) => (
<option key={type} value={type}>
{BrainCoordinateUtil.getConversionLabel(type)}
</option>
))}
</select>
</td>
<td>
<textarea cols={37} rows={5} wrap="off" value={matrixArea} readOnly />
</td>
</tr>
<tr>
<td>
<input type="button" onClick={onClickConvertButton} value="Convert" />
</td>
<td>
<input type="button" onClick={onClickClearButton} value="Clear" />
</td>
</tr>
</tbody>
</table>
</div>
);
};
export default NimgcenterBrainConv;
+29 -26
View File
@@ -1,34 +1,37 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Navigate, Route, Routes } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import jsonHowToUse from "./assets/howtouse.json";
import jsonUtilities from "./assets/utilities.json";
import NimgcenterBrainConv from "./NimgcenterBrainConv";
import type React from 'react';
import { Helmet } from 'react-helmet-async';
import { Navigate, Route, Routes } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import XoopsCode from '../common/lib/XoopsCode';
import type { MultiLang } from '../config';
import jsonHowToUse from './assets/howtouse.json';
import jsonUtilities from './assets/utilities.json';
import NimgcenterBrainConv from './NimgcenterBrainConv';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const NimgcenterUtilities: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<Helmet>
<title>NIMG Utilities</title>
</Helmet>
<Routes>
<Route path="" element={<XoopsCode lang={lang} text={jsonUtilities[lang]} dohtml={true} />} />
<Route path="brain_conv/" element={<NimgcenterBrainConv lang={lang} />} />
<Route path="brain_conv/howtouse.php" element={<XoopsCode lang={lang} text={jsonHowToUse[lang]} dohtml={true} />} />
<Route path="index.php" element={<Navigate to="/modules/xnpnimgcenter/utilities/" />} />
<Route path="brain_conv/index.php" element={<Navigate to="/modules/xnpnimgcenter/utilities/brain_conv/" />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
const { lang } = props;
return (
<>
<Helmet>
<title>NIMG Utilities</title>
</Helmet>
<Routes>
<Route path="" element={<XoopsCode lang={lang} text={jsonUtilities[lang]} dohtml={true} />} />
<Route path="brain_conv/" element={<NimgcenterBrainConv lang={lang} />} />
<Route
path="brain_conv/howtouse.php"
element={<XoopsCode lang={lang} text={jsonHowToUse[lang]} dohtml={true} />}
/>
<Route path="index.php" element={<Navigate to="/modules/xnpnimgcenter/utilities/" />} />
<Route path="brain_conv/index.php" element={<Navigate to="/modules/xnpnimgcenter/utilities/brain_conv/" />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
};
export default NimgcenterUtilities;
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -1 +1,4 @@
{"en":"<h1>\n Utilities\n<\/h1><br \/>\n<br \/>\n<table class=\"outer\">\n <tbody>\n <tr>\n <td width=\"10%\" style=\"vertical-align:middle; text-align:center;\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\"><img src=\"\/modules\/xnpnimgcenter\/utilities\/images\/brain_conv_ss.png\" alt=\"brainconv\" \/><\/a>\n <\/td>\n <td width=\"90%\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\">Brain Coordinate Converter<\/a><br \/>\n This utility is able to mutually convert several types of brain coordinates(Requires JavaScript).<br \/>\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/howtouse.php\">How to use<\/a>\n <\/td>\n <\/tr>\n <\/tbody>\n<\/table>","ja":"<h1>\n ユーティリティ\n<\/h1><br \/>\n<br \/>\n<table class=\"outer\">\n <tbody>\n <tr>\n <td width=\"10%\" style=\"vertical-align:middle; text-align:center;\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\"><img src=\"\/modules\/xnpnimgcenter\/utilities\/images\/brain_conv_ss.png\" alt=\"brainconv\" \/><\/a>\n <\/td>\n <td width=\"90%\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\">脳座標変換ツール<\/a><br \/>\n 数種類の脳座間の変換が行えます(要Javascript)。<br \/>\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/howtouse.php\">使い方<\/a>\n <\/td>\n <\/tr>\n <\/tbody>\n<\/table>"}
{
"en": "<h1>\n Utilities\n<\/h1><br \/>\n<br \/>\n<table class=\"outer\">\n <tbody>\n <tr>\n <td width=\"10%\" style=\"vertical-align:middle; text-align:center;\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\"><img src=\"\/modules\/xnpnimgcenter\/utilities\/images\/brain_conv_ss.png\" alt=\"brainconv\" \/><\/a>\n <\/td>\n <td width=\"90%\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\">Brain Coordinate Converter<\/a><br \/>\n This utility is able to mutually convert several types of brain coordinates(Requires JavaScript).<br \/>\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/howtouse.php\">How to use<\/a>\n <\/td>\n <\/tr>\n <\/tbody>\n<\/table>",
"ja": "<h1>\n ユーティリティ\n<\/h1><br \/>\n<br \/>\n<table class=\"outer\">\n <tbody>\n <tr>\n <td width=\"10%\" style=\"vertical-align:middle; text-align:center;\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\"><img src=\"\/modules\/xnpnimgcenter\/utilities\/images\/brain_conv_ss.png\" alt=\"brainconv\" \/><\/a>\n <\/td>\n <td width=\"90%\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\">脳座標変換ツール<\/a><br \/>\n 数種類の脳座間の変換が行えます(要Javascript)。<br \/>\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/howtouse.php\">使い方<\/a>\n <\/td>\n <\/tr>\n <\/tbody>\n<\/table>"
}
+87 -76
View File
@@ -1,75 +1,81 @@
export interface BrainCoordinate {
x: number;
y: number;
z: number;
x: number;
y: number;
z: number;
}
export type ConversionType = "icbm_spm2tal" | "tal2icbm_spm" | "icbm_fsl2tal" | "tal2icbm_fsl" | "icbm_other2tal" | "tal2icbm_other";
export type ConversionType =
| 'icbm_spm2tal'
| 'tal2icbm_spm'
| 'icbm_fsl2tal'
| 'tal2icbm_fsl'
| 'icbm_other2tal'
| 'tal2icbm_other';
type ConversionVector = readonly [number, number, number, number];
type ConversionMatrix = readonly [ConversionVector, ConversionVector, ConversionVector, ConversionVector];
interface ConversionMatrixData {
[key: string]: {
label: string;
matrix: ConversionMatrix;
};
[key: string]: {
label: string;
matrix: ConversionMatrix;
};
}
const conversionMatrix: ConversionMatrixData = {
icbm_spm2tal: {
label: "icbm_spm => Talairach",
matrix: [
[0.9254, 0.0024, -0.0118, -1.0207],
[-0.0048, 0.9316, -0.0871, -1.7667],
[0.0152, 0.0883, 0.8924, 4.0926],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_spm: {
label: "Talairach => icbm_spm",
matrix: [
[1.0804, -0.0041, 0.0139, 1.0387],
[0.0038, 1.0636, 0.1039, 1.4579],
[-0.0188, -0.1052, 1.1101, -4.748],
[0.0, 0.0, 0.0, 1.0],
],
},
icbm_fsl2tal: {
label: "icbm_fsl => Talairach",
matrix: [
[0.9464, 0.0034, -0.0026, -1.068],
[-0.0083, 0.9479, -0.058, -1.0239],
[0.0053, 0.0617, 0.901, 3.1883],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_fsl: {
label: "Talairach => icbm_fsl",
matrix: [
[1.0566, -0.004, 0.0028, 1.1155],
[0.0088, 1.0505, 0.0677, 0.8694],
[-0.0068, -0.0719, 1.1052, -3.6047],
[0.0, 0.0, 0.0, 1.0],
],
},
icbm_other2tal: {
label: "other(SPM99,Brain Voyager) => Talairach",
matrix: [
[0.9357, 0.0029, -0.0072, -1.0423],
[-0.0065, 0.9396, -0.0726, -1.394],
[0.0103, 0.0752, 0.8967, 3.6475],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_other: {
label: "Talairach => other(SPM99,Brain Voyager)",
matrix: [
[1.0686, -0.004, 0.0083, 1.0782],
[0.0064, 1.0574, 0.0857, 1.1682],
[-0.0128, -0.0886, 1.1079, -4.178],
[0.0, 0.0, 0.0, 1.0],
],
},
/*
icbm_spm2tal: {
label: 'icbm_spm => Talairach',
matrix: [
[0.9254, 0.0024, -0.0118, -1.0207],
[-0.0048, 0.9316, -0.0871, -1.7667],
[0.0152, 0.0883, 0.8924, 4.0926],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_spm: {
label: 'Talairach => icbm_spm',
matrix: [
[1.0804, -0.0041, 0.0139, 1.0387],
[0.0038, 1.0636, 0.1039, 1.4579],
[-0.0188, -0.1052, 1.1101, -4.748],
[0.0, 0.0, 0.0, 1.0],
],
},
icbm_fsl2tal: {
label: 'icbm_fsl => Talairach',
matrix: [
[0.9464, 0.0034, -0.0026, -1.068],
[-0.0083, 0.9479, -0.058, -1.0239],
[0.0053, 0.0617, 0.901, 3.1883],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_fsl: {
label: 'Talairach => icbm_fsl',
matrix: [
[1.0566, -0.004, 0.0028, 1.1155],
[0.0088, 1.0505, 0.0677, 0.8694],
[-0.0068, -0.0719, 1.1052, -3.6047],
[0.0, 0.0, 0.0, 1.0],
],
},
icbm_other2tal: {
label: 'other(SPM99,Brain Voyager) => Talairach',
matrix: [
[0.9357, 0.0029, -0.0072, -1.0423],
[-0.0065, 0.9396, -0.0726, -1.394],
[0.0103, 0.0752, 0.8967, 3.6475],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_other: {
label: 'Talairach => other(SPM99,Brain Voyager)',
matrix: [
[1.0686, -0.004, 0.0083, 1.0782],
[0.0064, 1.0574, 0.0857, 1.1682],
[-0.0128, -0.0886, 1.1079, -4.178],
[0.0, 0.0, 0.0, 1.0],
],
},
/*
tal2mni: {
label: 'Talairach => MNI(Matthew Brett,99)',
matrix: null // See also, http://eeg.sourceforge.net/doc_m2html/bioelectromagnetism/tal2mni.html
@@ -82,29 +88,34 @@ const conversionMatrix: ConversionMatrixData = {
};
const isConversionType = (type: string): boolean => {
return type in conversionMatrix;
return type in conversionMatrix;
};
const getConversionTypes = (): ConversionType[] => {
return Object.keys(conversionMatrix) as ConversionType[];
return Object.keys(conversionMatrix) as ConversionType[];
};
const getConversionLabel = (type: ConversionType): string => {
return conversionMatrix[type].label;
return conversionMatrix[type].label;
};
const getConversionMatrix = (type: ConversionType): ConversionMatrix => {
return conversionMatrix[type].matrix;
return conversionMatrix[type].matrix;
};
const mulMatrix = (mat: ConversionMatrix, vec: ConversionVector): ConversionVector => {
return [mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3], mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3], mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3], mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3]];
return [
mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3],
mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3],
mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3],
mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3],
];
};
const convertCoordinate = (type: ConversionType, coord: BrainCoordinate): BrainCoordinate => {
const vec: ConversionVector = [coord.x, coord.y, coord.z, 1.0];
const res = mulMatrix(conversionMatrix[type].matrix, vec);
return { x: res[0], y: res[1], z: res[2] };
const vec: ConversionVector = [coord.x, coord.y, coord.z, 1.0];
const res = mulMatrix(conversionMatrix[type].matrix, vec);
return { x: res[0], y: res[1], z: res[2] };
};
/*
@@ -142,11 +153,11 @@ function tal2mni(v) {
*/
const BrainCoordinateUtil = {
isConversionType,
getConversionTypes,
getConversionLabel,
getConversionMatrix,
convertCoordinate,
isConversionType,
getConversionTypes,
getConversionLabel,
getConversionMatrix,
convertCoordinate,
};
export default BrainCoordinateUtil;
+51 -51
View File
@@ -1,65 +1,65 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import Functions from "../functions";
import json3DBrowse from "./assets/3dbrowse.json";
import json3DBrowseUsage from "./assets/3dbrowse_usage.json";
import jsonAbout from "./assets/about.json";
import jsonAdditem from "./assets/additem.json";
import jsonIntroduction from "./assets/introduction.json";
import jsonNimgcenter from "./assets/nimgcenter.json";
import jsonNimgsearch from "./assets/nimgsearch.json";
import NimgdocsBulletinLog from "./NimgdocsBulletinLog";
import NimgdocsTutorilas from "./NimgdocsTutorials";
import type React from 'react';
import { Helmet } from 'react-helmet-async';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import XoopsCode from '../common/lib/XoopsCode';
import type { MultiLang } from '../config';
import Functions from '../functions';
import json3DBrowse from './assets/3dbrowse.json';
import json3DBrowseUsage from './assets/3dbrowse_usage.json';
import jsonAbout from './assets/about.json';
import jsonAdditem from './assets/additem.json';
import jsonIntroduction from './assets/introduction.json';
import jsonNimgcenter from './assets/nimgcenter.json';
import jsonNimgsearch from './assets/nimgsearch.json';
import NimgdocsBulletinLog from './NimgdocsBulletinLog';
import NimgdocsTutorilas from './NimgdocsTutorials';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const jsonDocuments = [
{ name: "about_NIMGPF", data: jsonAbout },
{ name: "additem", data: jsonAdditem },
{ name: "introduction", data: jsonIntroduction },
{ name: "nimgcenter", data: jsonNimgcenter },
{ name: "nimgsearch", data: jsonNimgsearch },
{ name: "3DBrowse", data: json3DBrowse },
{ name: "3DBrowse_usage", data: json3DBrowseUsage },
{ name: 'about_NIMGPF', data: jsonAbout },
{ name: 'additem', data: jsonAdditem },
{ name: 'introduction', data: jsonIntroduction },
{ name: 'nimgcenter', data: jsonNimgcenter },
{ name: 'nimgsearch', data: jsonNimgsearch },
{ name: '3DBrowse', data: json3DBrowse },
{ name: '3DBrowse_usage', data: json3DBrowseUsage },
];
const NimgdocsPage: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const docname = params.get("docname") || "";
const doc = jsonDocuments.find((value) => value.name === docname);
if (typeof doc !== "undefined") {
return (
<>
<Helmet>
<title>NIMG Documents - {Functions.siteTitle(lang)}</title>
</Helmet>
<XoopsCode lang={lang} text={doc.data[lang]} dohtml={true} />
</>
);
}
return <PageNotFound lang={lang} />;
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const docname = params.get('docname') || '';
const doc = jsonDocuments.find((value) => value.name === docname);
if (typeof doc !== 'undefined') {
return (
<>
<Helmet>
<title>NIMG Documents - {Functions.siteTitle(lang)}</title>
</Helmet>
<XoopsCode lang={lang} text={doc.data[lang]} dohtml={true} />
</>
);
}
return <PageNotFound lang={lang} />;
};
const Nimgdocs: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<Routes>
<Route path="" element={<Navigate to="/modules/nimgdocs/index.php?docname=about_NIMGPF" />} />
<Route path="index.php" element={<NimgdocsPage lang={lang} />} />
<Route path="tutorials/index.php" element={<Navigate to="/modules/nimgdocs/tutorials/" />} />
<Route path="tutorials/" element={<NimgdocsTutorilas lang={lang} />} />
<Route path="bulletin/log.php" element={<NimgdocsBulletinLog lang={lang} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
);
const { lang } = props;
return (
<Routes>
<Route path="" element={<Navigate to="/modules/nimgdocs/index.php?docname=about_NIMGPF" />} />
<Route path="index.php" element={<NimgdocsPage lang={lang} />} />
<Route path="tutorials/index.php" element={<Navigate to="/modules/nimgdocs/tutorials/" />} />
<Route path="tutorials/" element={<NimgdocsTutorilas lang={lang} />} />
<Route path="bulletin/log.php" element={<NimgdocsBulletinLog lang={lang} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
);
};
export default Nimgdocs;
+37 -37
View File
@@ -1,45 +1,45 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import Functions from "../functions";
import jsonBulletin from "./assets/bulletin.json";
import type React from 'react';
import { Helmet } from 'react-helmet-async';
import XoopsCode from '../common/lib/XoopsCode';
import type { MultiLang } from '../config';
import Functions from '../functions';
import jsonBulletin from './assets/bulletin.json';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const NimgdocsBulletinLog: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = Functions.mlang("[en]Expired Posts[/en][ja]掲載期間の終了した投稿[/ja]", lang);
const now = new Date().getTime() / 1000;
const logs = jsonBulletin.filter((log) => log.end_date < now && log.text[lang] !== "");
return (
<>
<Helmet>
<title>
{title} - NIMG Documents - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h1>{Functions.mlang("[en]Expired Posts[/en][ja]掲載期間の終了した投稿[/ja]", lang)}</h1>
{logs.map((log) => {
return (
<div key={log.id}>
{log.tags.map((tag) => (
<span key={tag} style={{ margin: "0 2px", padding: "0 2px", color: "#666", backgroundColor: "#eee" }}>
{tag}
</span>
))}
<XoopsCode lang={lang} text={log.text[lang]} dobr />
<div style={{ margin: "5px 0", textAlign: "right" }}>
{Functions.formatDate(log.post_date, "YYYY-MM-DD HH:mm:ss")} Posted by <b>{log.author}</b>
</div>
<hr style={{ margin: "10px 0", border: "none", borderTop: "1px dotted #ccc" }} />
</div>
);
})}
</>
);
const { lang } = props;
const title = Functions.mlang('[en]Expired Posts[/en][ja]掲載期間の終了した投稿[/ja]', lang);
const now = new Date().getTime() / 1000;
const logs = jsonBulletin.filter((log) => log.end_date < now && log.text[lang] !== '');
return (
<>
<Helmet>
<title>
{title} - NIMG Documents - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h1>{Functions.mlang('[en]Expired Posts[/en][ja]掲載期間の終了した投稿[/ja]', lang)}</h1>
{logs.map((log) => {
return (
<div key={log.id}>
{log.tags.map((tag) => (
<span key={tag} style={{ margin: '0 2px', padding: '0 2px', color: '#666', backgroundColor: '#eee' }}>
{tag}
</span>
))}
<XoopsCode lang={lang} text={log.text[lang]} dobr />
<div style={{ margin: '5px 0', textAlign: 'right' }}>
{Functions.formatDate(log.post_date, 'YYYY-MM-DD HH:mm:ss')} Posted by <b>{log.author}</b>
</div>
<hr style={{ margin: '10px 0', border: 'none', borderTop: '1px dotted #ccc' }} />
</div>
);
})}
</>
);
};
export default NimgdocsBulletinLog;
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -1 +1,4 @@
{"en":"<p>\n This document describe the MRIdatabase developed by <a href=\"http:\/\/www.aist.go.jp\/\">AIST<\/a>.<br \/>\n <br \/>\n This samples of 3D-Browsing of brain image follow. Please try out.\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_jm_eng.php\">3D-Browsing of a monkey brain image<\/a>\n <\/li>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_hu_eng.php\">3D-Browsing of Human brain image<\/a>\n <\/li>\n<\/ul>\n<p>\n Please refer to the following link for usage.\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/nimgdocs\/index.php?docname=3DBrowse_usage\">How to use 3D-Browsing<\/a>\n <\/li>\n<\/ul>","ja":"<p>\n これは、<a href=\"http:\/\/www.aist.go.jp\/\">産総研<\/a>で開発されているMRIデータベースに関するドキュメントです。<br \/>\n <br \/>\n 脳画像3Dブラウジングのサンプルを用意いたしました。お試しください。\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_jm_jpn.php\">猿の脳画像3Dブラウジング<\/a>\n <\/li>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_hu_jpn.php\">人の脳画像3Dブラウジング<\/a>\n <\/li>\n<\/ul>\n<p>\n 使い方については下記のリンクをご参照ください。\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/nimgdocs\/index.php?docname=3DBrowse_usage\">3Dブラウジングの使い方<\/a>\n <\/li>\n<\/ul>"}
{
"en": "<p>\n This document describe the MRIdatabase developed by <a href=\"http:\/\/www.aist.go.jp\/\">AIST<\/a>.<br \/>\n <br \/>\n This samples of 3D-Browsing of brain image follow. Please try out.\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_jm_eng.php\">3D-Browsing of a monkey brain image<\/a>\n <\/li>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_hu_eng.php\">3D-Browsing of Human brain image<\/a>\n <\/li>\n<\/ul>\n<p>\n Please refer to the following link for usage.\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/nimgdocs\/index.php?docname=3DBrowse_usage\">How to use 3D-Browsing<\/a>\n <\/li>\n<\/ul>",
"ja": "<p>\n これは、<a href=\"http:\/\/www.aist.go.jp\/\">産総研<\/a>で開発されているMRIデータベースに関するドキュメントです。<br \/>\n <br \/>\n 脳画像3Dブラウジングのサンプルを用意いたしました。お試しください。\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_jm_jpn.php\">猿の脳画像3Dブラウジング<\/a>\n <\/li>\n <li>\n <a href=\"\/modules\/brainexplorer\/index_hu_jpn.php\">人の脳画像3Dブラウジング<\/a>\n <\/li>\n<\/ul>\n<p>\n 使い方については下記のリンクをご参照ください。\n<\/p>\n<ul>\n <li>\n <a href=\"\/modules\/nimgdocs\/index.php?docname=3DBrowse_usage\">3Dブラウジングの使い方<\/a>\n <\/li>\n<\/ul>"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -1 +1,4 @@
{"en":"<iframe src=\"docs\/nimgcenter_document\/index.html\" width=\"100%\" height=\"600\">Use a browser corresponding to the inline-frame.<\/iframe>","ja":"<iframe src=\"docs\/nimgcenter_document\/index.html\" width=\"100%\" height=\"600\">インラインフレームに対応したブラウザでご覧ください。<\/iframe>"}
{
"en": "<iframe src=\"docs\/nimgcenter_document\/index.html\" width=\"100%\" height=\"600\">Use a browser corresponding to the inline-frame.<\/iframe>",
"ja": "<iframe src=\"docs\/nimgcenter_document\/index.html\" width=\"100%\" height=\"600\">インラインフレームに対応したブラウザでご覧ください。<\/iframe>"
}
+4 -1
View File
@@ -1 +1,4 @@
{"en":"<iframe src=\"docs\/nimgsearch_document\/index.html\" width=\"100%\" height=\"600\">Use a browser corresponding to the inline-frame.<\/iframe>","ja":"<iframe src=\"docs\/nimgsearch_document\/index.html\" width=\"100%\" height=\"600\">インラインフレームに対応したブラウザでご覧ください。<\/iframe>"}
{
"en": "<iframe src=\"docs\/nimgsearch_document\/index.html\" width=\"100%\" height=\"600\">Use a browser corresponding to the inline-frame.<\/iframe>",
"ja": "<iframe src=\"docs\/nimgsearch_document\/index.html\" width=\"100%\" height=\"600\">インラインフレームに対応したブラウザでご覧ください。<\/iframe>"
}
+122 -122
View File
@@ -1,323 +1,323 @@
.container {
background-color: #264955;
padding: 5px;
width: 670px;
background-color: #264955;
padding: 5px;
width: 670px;
}
.headerPanel {
background-color: #ffc;
height: 24px;
padding: 4px 5px;
width: 660px;
background-color: #ffc;
height: 24px;
padding: 4px 5px;
width: 660px;
}
.headerMessage {
float: left;
width: 405px;
float: left;
width: 405px;
}
.headerLinks {
float: right;
width: 245px;
text-align: right;
float: right;
width: 245px;
text-align: right;
}
.headerLinks span {
cursor: pointer;
color: blue;
margin: 0 7px;
text-decoration: underline;
cursor: pointer;
color: blue;
margin: 0 7px;
text-decoration: underline;
}
.mainPanel {
position: relative;
position: relative;
}
.brainPanel {
width: 660px;
padding: 10px 0;
color: white;
position: absolute;
width: 660px;
padding: 10px 0;
color: white;
position: absolute;
}
.mainPanel:global(.large),
.brainPanel:global(.large) {
height: 430px;
height: 430px;
}
.mainPanel:global(.medium),
.brainPanel:global(.medium) {
height: 330px;
height: 330px;
}
.mainPanel:global(.small),
.brainPanel:global(.small) {
height: 280px;
height: 280px;
}
.brainViewY,
.brainViewX,
.brainViewZ,
.coordView {
position: absolute;
position: absolute;
}
.brainViewX:global(.large) {
left: 184px;
left: 184px;
}
.brainViewX:global(.medium) {
left: 138.5px;
left: 138.5px;
}
.brainViewX:global(.small) {
left: 93px;
left: 93px;
}
.brainViewZ:global(.large) {
top: 194px;
top: 194px;
}
.brainViewZ:global(.medium) {
top: 148.5px;
top: 148.5px;
}
.brainViewZ:global(.small) {
top: 103px;
top: 103px;
}
.coordView:global(.large) {
top: 202px;
left: 192px;
top: 202px;
left: 192px;
}
.coordView:global(.medium) {
top: 158.5px;
left: 146.5px;
top: 158.5px;
left: 146.5px;
}
.coordView:global(.small) {
top: 111px;
left: 101px;
top: 111px;
left: 101px;
}
.brainViewX canvas,
.brainViewY canvas,
.brainViewZ canvas {
background-color: black;
background-color: black;
}
.searchPanel {
position: absolute;
white-space: nowrap;
position: absolute;
white-space: nowrap;
}
.searchPanel:global(.large) {
top: 222px;
left: 192px;
top: 222px;
left: 192px;
}
.searchPanel:global(.medium) {
top: 178.5px;
left: 146.5px;
top: 178.5px;
left: 146.5px;
}
.searchPanel:global(.small) {
top: 131px;
left: 101px;
top: 131px;
left: 101px;
}
.searchConditions {
margin: 5px 0;
margin: 5px 0;
}
.searchButton {
margin: 10px 0;
margin: 10px 0;
}
.searchConditions input[type="text"] {
padding: 2px;
border: 1px solid #ccc;
padding: 2px;
border: 1px solid #ccc;
}
.searchConditions span:not(:first-child) {
padding: 0 0 0 5px;
padding: 0 0 0 5px;
}
.popupPanel {
position: absolute;
right: 0;
z-index: 1;
width: 250px;
padding: 5px;
background-color: #ffc;
position: absolute;
right: 0;
z-index: 1;
width: 250px;
padding: 5px;
background-color: #ffc;
}
.settings {
font-size: 85%;
font-size: 85%;
}
.settings dl {
margin: 0;
margin: 0;
}
.settings dt {
margin: 5px 0 3px;
font-weight: bold;
margin: 5px 0 3px;
font-weight: bold;
}
.settings dd {
margin: 0 0 0 5px;
vertical-align: middle;
margin: 0 0 0 5px;
vertical-align: middle;
}
.settings dd span:not(:first-child) {
margin: 0 0 0 5px;
margin: 0 0 0 5px;
}
.help {
text-align: center;
text-align: center;
}
.help div {
margin: 5px 0 3px;
margin: 5px 0 3px;
}
.help span {
font-weight: bold;
font-weight: bold;
}
.footerPanel {
background-color: white;
background-color: white;
}
.navTabs {
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 0;
list-style: none;
}
.navTabs > li {
width: 165px;
margin-bottom: -1px;
padding: 3px 10px;
background-color: #e0e0e0;
border: 1px solid #c0c0c0;
cursor: pointer;
text-align: center;
width: 165px;
margin-bottom: -1px;
padding: 3px 10px;
background-color: #e0e0e0;
border: 1px solid #c0c0c0;
cursor: pointer;
text-align: center;
}
.navTabs > li:global(.actived) {
background-color: white;
font-weight: bold;
border-bottom: none;
background-color: white;
font-weight: bold;
border-bottom: none;
}
.tabContent {
background-color: white;
border: 1px solid #c0c0c0;
padding: 10px 10px;
background-color: white;
border: 1px solid #c0c0c0;
padding: 10px 10px;
}
.tabBrainArea > div {
float: left;
float: left;
}
.tabBrainAreaSource,
.tabBrainAreaList {
width: 265px;
width: 265px;
}
.tabBrainAreaButtons {
width: 108px;
padding: 50px 0;
text-align: center;
width: 108px;
padding: 50px 0;
text-align: center;
}
.tabBrainAreaSelect {
margin: 5px 0;
width: 265px;
margin: 5px 0;
width: 265px;
}
.tabItemRetrievalTable th {
background-color: #c0c0c0;
background-color: #c0c0c0;
}
.tabItemRetrievalTable th:nth-child(2) {
width: 500px;
width: 500px;
}
.tabItemRetrievalTable td {
background-color: #f5deb3;
background-color: #f5deb3;
}
.tabItemRetrievalTable > tbody > tr > td:first-child {
font-weight: bold;
text-align: center;
font-weight: bold;
text-align: center;
}
.tabItemRetrievalTable :global(.advancedSearchCondition:not(:first-child)) {
margin: 5px 0;
margin: 5px 0;
}
.tabSearchResult a {
color: #4169e1;
color: #4169e1;
}
.tabSearchResultPageNavi {
text-align: right;
margin: 3px 0;
font-weight: bold;
text-align: right;
margin: 3px 0;
font-weight: bold;
}
.tabSearchResultPageNavi span:not(:first-child),
.tabSearchResultPageNavi a:not(:first-child) {
margin-left: 3px;
margin-left: 3px;
}
.tabSearchResultSortCondition {
text-align: center;
margin: 3px 0;
text-align: center;
margin: 3px 0;
}
.tabSearchResultSortCondition span:not(:first-child) {
margin-left: 3px;
margin-left: 3px;
}
.tabSearchResultItems li,
.tabMyItems li {
border-bottom: 1px solid #90ee90;
border-bottom: 1px solid #90ee90;
}
.tabMyItems h3 {
border-bottom: 1px solid #8b0000;
border-left: 15px solid #8b0000;
padding-left: 7px;
color: #8b0000;
border-bottom: 1px solid #8b0000;
border-left: 15px solid #8b0000;
padding-left: 7px;
color: #8b0000;
}
.listItem a {
color: #4169e1;
font-size: 110%;
text-decoration: underline;
font-weight: bold;
color: #4169e1;
font-size: 110%;
text-decoration: underline;
font-weight: bold;
}
.listItem input[type="button"] {
margin: 0 3px;
padding: 0 3px;
margin: 0 3px;
padding: 0 3px;
}
.listItemCoordinates {
margin-left: 20px;
margin-left: 20px;
}
.headerPanel::after,
.mainPanel::after,
.footerPanel::after,
.tabContent::after {
content: "";
display: block;
clear: both;
content: "";
display: block;
clear: both;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+260 -204
View File
@@ -1,268 +1,324 @@
import BrainCoordinateUtil, { BrainCoordinate } from "../../nimgcenter/lib/BrainCoordinateUtil";
import AdvancedSearchQuery from "../../xoonips/lib/AdvancedSearchQuery";
import ItemUtil, { ItemNimgcenter, SearchCallbackFunc, SortCondition } from "../../xoonips/lib/ItemUtil";
import brainArea from "../assets/brainarea.json";
import BrainCoordinateUtil, { type BrainCoordinate } from '../../nimgcenter/lib/BrainCoordinateUtil';
import AdvancedSearchQuery from '../../xoonips/lib/AdvancedSearchQuery';
import ItemUtil, { type ItemNimgcenter, type SearchCallbackFunc, type SortCondition } from '../../xoonips/lib/ItemUtil';
import brainArea from '../assets/brainarea.json';
export type BrainCoordinateAxis = "x" | "y" | "z";
export type BrainCoordinateAxis = 'x' | 'y' | 'z';
interface BrainCoordinateBase<T> {
x: T;
y: T;
z: T;
x: T;
y: T;
z: T;
}
export type BrainCoordinateString = BrainCoordinateBase<string>;
export type BrainCoordinateType = "mni" | "talairach" | "unspecified";
export type BrainImageSize = "large" | "medium" | "small";
export type SearchPaperType = "all" | "normal" | "patients";
export type BrainCoordinateType = 'mni' | 'talairach' | 'unspecified';
export type BrainImageSize = 'large' | 'medium' | 'small';
export type SearchPaperType = 'all' | 'normal' | 'patients';
const brainImageConfig = {
x: { pics: 91, width: 109, height: 91, range: { num: 180, start: -90, end: 90 } },
y: { pics: 109, width: 91, height: 91, range: { num: 216, start: -126, end: 90 } },
z: { pics: 91, width: 91, height: 109, range: { num: 180, start: 108, end: -72 } },
x: { pics: 91, width: 109, height: 91, range: { num: 180, start: -90, end: 90 } },
y: { pics: 109, width: 91, height: 91, range: { num: 216, start: -126, end: 90 } },
z: { pics: 91, width: 91, height: 109, range: { num: 180, start: 108, end: -72 } },
};
const brainImageFactor = {
large: 2.0,
medium: 1.5,
small: 1.0,
large: 2.0,
medium: 1.5,
small: 1.0,
};
const brainImageSpotConfig = {
gapSize: [
{ gap: 4, size: 8 },
{ gap: 8, size: 6 },
{ gap: 12, size: 4 },
{ gap: 16, size: 2 },
],
color: ["aqua", "yellow", "green", "red", "purple", "orange", "pink", "blue", "springgreen", "silver"],
gapSize: [
{ gap: 4, size: 8 },
{ gap: 8, size: 6 },
{ gap: 12, size: 4 },
{ gap: 16, size: 2 },
],
color: ['aqua', 'yellow', 'green', 'red', 'purple', 'orange', 'pink', 'blue', 'springgreen', 'silver'],
};
const fixRange = (value: number, min: number, max: number) => (value < min ? min : value > max ? max : value);
const loadImage = async (url: string): Promise<HTMLImageElement> => {
const image = new Image();
await new Promise((resolve, reject) => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", reject, { once: true });
image.src = url;
});
return image;
const image = new Image();
await new Promise((resolve, reject) => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', reject, { once: true });
image.src = url;
});
return image;
};
const getResourceUrl = (name: string): string => {
return `/modules/nimgsearch/res/${name}`;
return `/modules/nimgsearch/res/${name}`;
};
const getBrainImageUrl = (brainImageId: number, axis: BrainCoordinateAxis): string => {
return getResourceUrl(`${axis}/brain${brainImageId}.png`);
return getResourceUrl(`${axis}/brain${brainImageId}.png`);
};
const getBrainAreaImageUrl = (brainAreaId: number, brainImageId: number, axis: BrainCoordinateAxis): string => {
const level = brainAreaId % 10;
return getResourceUrl(`level${level}/${brainAreaId}/${axis}/brain${brainImageId}.png`);
const level = brainAreaId % 10;
return getResourceUrl(`level${level}/${brainAreaId}/${axis}/brain${brainImageId}.png`);
};
const roundBrainCoordinate = (coord: BrainCoordinate) => {
return { x: Math.round(coord.x), y: Math.round(coord.y), z: Math.round(coord.z) };
return { x: Math.round(coord.x), y: Math.round(coord.y), z: Math.round(coord.z) };
};
const fixBrainCoordinate = (coord: BrainCoordinate, type: BrainCoordinateType): BrainCoordinate => {
const mniCoord = type === "talairach" ? BrainCoordinateUtil.convertCoordinate("tal2icbm_spm", coord) : { ...coord };
const fixedMniCoord = {
x: fixRange(mniCoord.x, brainImageConfig.x.range.start, brainImageConfig.x.range.end),
y: fixRange(mniCoord.y, brainImageConfig.y.range.start, brainImageConfig.y.range.end),
z: fixRange(mniCoord.z, brainImageConfig.z.range.end, brainImageConfig.z.range.start),
};
return roundBrainCoordinate(type === "talairach" ? BrainCoordinateUtil.convertCoordinate("icbm_spm2tal", fixedMniCoord) : fixedMniCoord);
const mniCoord = type === 'talairach' ? BrainCoordinateUtil.convertCoordinate('tal2icbm_spm', coord) : { ...coord };
const fixedMniCoord = {
x: fixRange(mniCoord.x, brainImageConfig.x.range.start, brainImageConfig.x.range.end),
y: fixRange(mniCoord.y, brainImageConfig.y.range.start, brainImageConfig.y.range.end),
z: fixRange(mniCoord.z, brainImageConfig.z.range.end, brainImageConfig.z.range.start),
};
return roundBrainCoordinate(
type === 'talairach' ? BrainCoordinateUtil.convertCoordinate('icbm_spm2tal', fixedMniCoord) : fixedMniCoord,
);
};
const getBrainCoordinateFromOffset = (currentCoord: BrainCoordinate, type: BrainCoordinateType, offset: { x: number; y: number }, imageSize: BrainImageSize, axis: BrainCoordinateAxis): BrainCoordinate => {
const factor = brainImageFactor[imageSize];
const mniCoord = type === "talairach" ? BrainCoordinateUtil.convertCoordinate("tal2icbm_spm", currentCoord) : { ...currentCoord };
switch (axis) {
case "y":
mniCoord.x = (offset.x * brainImageConfig.x.range.num) / brainImageConfig.y.width / factor + brainImageConfig.x.range.start;
mniCoord.z = (offset.y * -brainImageConfig.z.range.num) / brainImageConfig.y.height / factor + brainImageConfig.z.range.start;
break;
case "x":
mniCoord.y = (offset.x * brainImageConfig.y.range.num) / brainImageConfig.x.width / factor + brainImageConfig.y.range.start;
mniCoord.z = (offset.y * -brainImageConfig.z.range.num) / brainImageConfig.x.height / factor + brainImageConfig.z.range.start;
break;
case "z":
mniCoord.x = (offset.x * brainImageConfig.x.range.num) / brainImageConfig.z.width / factor + brainImageConfig.x.range.start;
mniCoord.y = (offset.y * -brainImageConfig.y.range.num) / brainImageConfig.z.height / factor + brainImageConfig.y.range.end;
break;
}
return roundBrainCoordinate(type === "talairach" ? BrainCoordinateUtil.convertCoordinate("icbm_spm2tal", mniCoord) : mniCoord);
const getBrainCoordinateFromOffset = (
currentCoord: BrainCoordinate,
type: BrainCoordinateType,
offset: { x: number; y: number },
imageSize: BrainImageSize,
axis: BrainCoordinateAxis,
): BrainCoordinate => {
const factor = brainImageFactor[imageSize];
const mniCoord =
type === 'talairach' ? BrainCoordinateUtil.convertCoordinate('tal2icbm_spm', currentCoord) : { ...currentCoord };
switch (axis) {
case 'y':
mniCoord.x =
(offset.x * brainImageConfig.x.range.num) / brainImageConfig.y.width / factor + brainImageConfig.x.range.start;
mniCoord.z =
(offset.y * -brainImageConfig.z.range.num) / brainImageConfig.y.height / factor +
brainImageConfig.z.range.start;
break;
case 'x':
mniCoord.y =
(offset.x * brainImageConfig.y.range.num) / brainImageConfig.x.width / factor + brainImageConfig.y.range.start;
mniCoord.z =
(offset.y * -brainImageConfig.z.range.num) / brainImageConfig.x.height / factor +
brainImageConfig.z.range.start;
break;
case 'z':
mniCoord.x =
(offset.x * brainImageConfig.x.range.num) / brainImageConfig.z.width / factor + brainImageConfig.x.range.start;
mniCoord.y =
(offset.y * -brainImageConfig.y.range.num) / brainImageConfig.z.height / factor + brainImageConfig.y.range.end;
break;
}
return roundBrainCoordinate(
type === 'talairach' ? BrainCoordinateUtil.convertCoordinate('icbm_spm2tal', mniCoord) : mniCoord,
);
};
const getOffsetFromBrainCoordinate = (coord: BrainCoordinate, type: BrainCoordinateType, imageSize: BrainImageSize, axis: BrainCoordinateAxis) => {
const mniCoord = type === "talairach" ? BrainCoordinateUtil.convertCoordinate("tal2icbm_spm", coord) : { ...coord };
const factor = brainImageFactor[imageSize];
const offset = { x: 0, y: 0 };
switch (axis) {
case "y":
offset.x = ((mniCoord.x - brainImageConfig.x.range.start) / brainImageConfig.x.range.num) * brainImageConfig.y.width;
offset.y = ((mniCoord.z - brainImageConfig.z.range.start) / -brainImageConfig.z.range.num) * brainImageConfig.y.height;
break;
case "x":
offset.x = ((mniCoord.y - brainImageConfig.y.range.start) / brainImageConfig.y.range.num) * brainImageConfig.x.width;
offset.y = ((mniCoord.z - brainImageConfig.z.range.start) / -brainImageConfig.z.range.num) * brainImageConfig.x.height;
break;
case "z":
offset.x = ((mniCoord.x - brainImageConfig.x.range.start) / brainImageConfig.x.range.num) * brainImageConfig.z.width;
offset.y = ((mniCoord.y - brainImageConfig.y.range.end) / -brainImageConfig.y.range.num) * brainImageConfig.z.height;
break;
}
return {
x: fixRange(Math.round(offset.x * factor), 0, Math.round(brainImageConfig[axis].width * factor)),
y: fixRange(Math.round(offset.y * factor), 0, Math.round(brainImageConfig[axis].height * factor)),
};
const getOffsetFromBrainCoordinate = (
coord: BrainCoordinate,
type: BrainCoordinateType,
imageSize: BrainImageSize,
axis: BrainCoordinateAxis,
) => {
const mniCoord = type === 'talairach' ? BrainCoordinateUtil.convertCoordinate('tal2icbm_spm', coord) : { ...coord };
const factor = brainImageFactor[imageSize];
const offset = { x: 0, y: 0 };
switch (axis) {
case 'y':
offset.x =
((mniCoord.x - brainImageConfig.x.range.start) / brainImageConfig.x.range.num) * brainImageConfig.y.width;
offset.y =
((mniCoord.z - brainImageConfig.z.range.start) / -brainImageConfig.z.range.num) * brainImageConfig.y.height;
break;
case 'x':
offset.x =
((mniCoord.y - brainImageConfig.y.range.start) / brainImageConfig.y.range.num) * brainImageConfig.x.width;
offset.y =
((mniCoord.z - brainImageConfig.z.range.start) / -brainImageConfig.z.range.num) * brainImageConfig.x.height;
break;
case 'z':
offset.x =
((mniCoord.x - brainImageConfig.x.range.start) / brainImageConfig.x.range.num) * brainImageConfig.z.width;
offset.y =
((mniCoord.y - brainImageConfig.y.range.end) / -brainImageConfig.y.range.num) * brainImageConfig.z.height;
break;
}
return {
x: fixRange(Math.round(offset.x * factor), 0, Math.round(brainImageConfig[axis].width * factor)),
y: fixRange(Math.round(offset.y * factor), 0, Math.round(brainImageConfig[axis].height * factor)),
};
};
const getBrainImageId = (coord: BrainCoordinate, axis: BrainCoordinateAxis): number => {
const im = brainImageConfig[axis];
const offset = coord[axis] - (axis === "z" ? im.range.end : im.range.start);
return Math.floor(offset / 2 + 1);
const im = brainImageConfig[axis];
const offset = coord[axis] - (axis === 'z' ? im.range.end : im.range.start);
return Math.floor(offset / 2 + 1);
};
const getBrainAreaImage = async (brainAreaIds: number[], brainImageId: number, axis: BrainCoordinateAxis): Promise<HTMLImageElement> => {
const canvas = document.createElement("canvas");
canvas.width = brainImageConfig[axis].width;
canvas.height = brainImageConfig[axis].height;
const ctx = canvas.getContext("2d");
if (ctx !== null) {
const areas = await Promise.all(brainAreaIds.map(async (brainAreaId) => loadImage(getBrainAreaImageUrl(brainAreaId, brainImageId, axis))));
areas.forEach((area) => {
ctx.drawImage(area, 0, 0, canvas.width, canvas.height);
});
const idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let ii = 0; ii < idata.data.length; ii += 4) {
if (idata.data[ii + 3] > 0) {
idata.data[ii] = 0xff;
idata.data[ii + 1] = 0;
idata.data[ii + 2] = 0;
idata.data[ii + 3] = 0xff;
}
}
ctx.putImageData(idata, 0, 0);
const getBrainAreaImage = async (
brainAreaIds: number[],
brainImageId: number,
axis: BrainCoordinateAxis,
): Promise<HTMLImageElement> => {
const canvas = document.createElement('canvas');
canvas.width = brainImageConfig[axis].width;
canvas.height = brainImageConfig[axis].height;
const ctx = canvas.getContext('2d');
if (ctx !== null) {
const areas = await Promise.all(
brainAreaIds.map(async (brainAreaId) => loadImage(getBrainAreaImageUrl(brainAreaId, brainImageId, axis))),
);
areas.forEach((area) => {
ctx.drawImage(area, 0, 0, canvas.width, canvas.height);
});
const idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let ii = 0; ii < idata.data.length; ii += 4) {
if (idata.data[ii + 3] > 0) {
idata.data[ii] = 0xff;
idata.data[ii + 1] = 0;
idata.data[ii + 2] = 0;
idata.data[ii + 3] = 0xff;
}
}
const image = await loadImage(canvas.toDataURL("image/gif"));
return image;
ctx.putImageData(idata, 0, 0);
}
const image = await loadImage(canvas.toDataURL('image/gif'));
return image;
};
const updateBrainImage = async (canvas: HTMLCanvasElement, coord: BrainCoordinate, type: BrainCoordinateType, axis: BrainCoordinateAxis, imageSize: BrainImageSize, brainAreaIds: number[], showLines: boolean, myItems: ItemNimgcenter[]): Promise<void> => {
const ctx = canvas.getContext("2d");
if (ctx !== null) {
const factor = brainImageFactor[imageSize];
const width = Math.round(brainImageConfig[axis].width * factor);
const height = Math.round(brainImageConfig[axis].height * factor);
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
const brainImageId = getBrainImageId(coord, axis);
const brainImage = await loadImage(getBrainImageUrl(brainImageId, axis));
ctx.drawImage(brainImage, 0, 0, width, height);
if (brainAreaIds.length > 0) {
const brainAreaImage = await getBrainAreaImage(brainAreaIds, brainImageId, axis);
ctx.save();
ctx.globalAlpha = 0.3;
ctx.drawImage(brainAreaImage, 0, 0, width, height);
ctx.restore();
}
if (myItems.length > 0) {
myItems.forEach((item, index) => {
ctx.save();
ctx.fillStyle = brainImageSpotConfig.color[index % 10];
ctx.globalAlpha = 0.6;
item.coordinate.forEach((spotCoord) => {
const gapSize = brainImageSpotConfig.gapSize.find((value) => Math.abs(coord[axis] - spotCoord[axis]) <= value.gap);
if (typeof gapSize !== "undefined") {
const offset = getOffsetFromBrainCoordinate(spotCoord, type, imageSize, axis);
ctx.beginPath();
ctx.arc(offset.x + 0.5, offset.y - 0.5, gapSize.size, 0, Math.PI * 2, true);
ctx.fill();
}
});
ctx.restore();
});
}
if (showLines) {
const offset = getOffsetFromBrainCoordinate(coord, type, imageSize, axis);
ctx.save();
ctx.strokeStyle = "#8080c0";
ctx.lineWidth = 2.5;
const updateBrainImage = async (
canvas: HTMLCanvasElement,
coord: BrainCoordinate,
type: BrainCoordinateType,
axis: BrainCoordinateAxis,
imageSize: BrainImageSize,
brainAreaIds: number[],
showLines: boolean,
myItems: ItemNimgcenter[],
): Promise<void> => {
const ctx = canvas.getContext('2d');
if (ctx !== null) {
const factor = brainImageFactor[imageSize];
const width = Math.round(brainImageConfig[axis].width * factor);
const height = Math.round(brainImageConfig[axis].height * factor);
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
const brainImageId = getBrainImageId(coord, axis);
const brainImage = await loadImage(getBrainImageUrl(brainImageId, axis));
ctx.drawImage(brainImage, 0, 0, width, height);
if (brainAreaIds.length > 0) {
const brainAreaImage = await getBrainAreaImage(brainAreaIds, brainImageId, axis);
ctx.save();
ctx.globalAlpha = 0.3;
ctx.drawImage(brainAreaImage, 0, 0, width, height);
ctx.restore();
}
if (myItems.length > 0) {
myItems.forEach((item, index) => {
ctx.save();
ctx.fillStyle = brainImageSpotConfig.color[index % 10];
ctx.globalAlpha = 0.6;
item.coordinate.forEach((spotCoord) => {
const gapSize = brainImageSpotConfig.gapSize.find(
(value) => Math.abs(coord[axis] - spotCoord[axis]) <= value.gap,
);
if (typeof gapSize !== 'undefined') {
const offset = getOffsetFromBrainCoordinate(spotCoord, type, imageSize, axis);
ctx.beginPath();
ctx.moveTo(offset.x + 0.5, 0);
ctx.lineTo(offset.x + 0.5, height);
ctx.moveTo(0, offset.y - 0.5);
ctx.lineTo(width, offset.y - 0.5);
ctx.stroke();
ctx.restore();
}
ctx.arc(offset.x + 0.5, offset.y - 0.5, gapSize.size, 0, Math.PI * 2, true);
ctx.fill();
}
});
ctx.restore();
});
}
if (showLines) {
const offset = getOffsetFromBrainCoordinate(coord, type, imageSize, axis);
ctx.save();
ctx.strokeStyle = '#8080c0';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(offset.x + 0.5, 0);
ctx.lineTo(offset.x + 0.5, height);
ctx.moveTo(0, offset.y - 0.5);
ctx.lineTo(width, offset.y - 0.5);
ctx.stroke();
ctx.restore();
}
}
};
const getBrainAreaNames = (level: number): string[] => {
if (level < 1 || level > 5) {
return [];
}
return brainArea[level - 1];
if (level < 1 || level > 5) {
return [];
}
return brainArea[level - 1];
};
const getBrainAreaName = (id: number): string => {
const level = (id % 10) - 1;
const index = Math.floor(id / 10) - 1;
if (brainArea.length <= level || brainArea[level].length <= index) {
return "";
}
return brainArea[level][index];
const level = (id % 10) - 1;
const index = Math.floor(id / 10) - 1;
if (brainArea.length <= level || brainArea[level].length <= index) {
return '';
}
return brainArea[level][index];
};
const getBrainAreaId = (level: number, index: number): number => {
return (index + 1) * 10 + level;
return (index + 1) * 10 + level;
};
const searchXoonipsItems = (coord: BrainCoordinate, radius: number, coordType: BrainCoordinateType, searchType: SearchPaperType, sortCondition: SortCondition, advancedQeury: AdvancedSearchQuery, andOr: string, func: SearchCallbackFunc) => {
const searchMain = (baseItemIds: number[]) => {
const query = new AdvancedSearchQuery();
const coordTypeMap = { mni: "M", talairach: "T", unspecified: "U" };
query.set("nimgcenter", "tx", coord.x.toString());
query.set("nimgcenter", "ty", coord.y.toString());
query.set("nimgcenter", "tz", coord.z.toString());
query.set("nimgcenter", "radius", radius.toString());
query.set("nimgcenter", "coord_type", coordTypeMap[coordType]);
query.set("nimgcenter", "coord_type_conversion", "yes");
if (andOr !== "") {
query.set("nimgcenter", "nimgsearch_andor", andOr);
}
if (searchType !== "all") {
query.set("nimgcenter", "index_id", searchType === "normal" ? "4375" : "4376");
}
if (baseItemIds.length > 0) {
query.set("nimgcenter", "baseitem_id", baseItemIds.join(","));
}
ItemUtil.getListByAdvancedSearchQuery(query, sortCondition, func);
};
if (!advancedQeury.empty()) {
ItemUtil.getListByAdvancedSearchQuery(advancedQeury, null, (result) => {
const baseItemIds = result.data.map((item) => item.item_id);
searchMain(baseItemIds);
});
} else {
searchMain([]);
const searchXoonipsItems = (
coord: BrainCoordinate,
radius: number,
coordType: BrainCoordinateType,
searchType: SearchPaperType,
sortCondition: SortCondition,
advancedQeury: AdvancedSearchQuery,
andOr: string,
func: SearchCallbackFunc,
) => {
const searchMain = (baseItemIds: number[]) => {
const query = new AdvancedSearchQuery();
const coordTypeMap = { mni: 'M', talairach: 'T', unspecified: 'U' };
query.set('nimgcenter', 'tx', coord.x.toString());
query.set('nimgcenter', 'ty', coord.y.toString());
query.set('nimgcenter', 'tz', coord.z.toString());
query.set('nimgcenter', 'radius', radius.toString());
query.set('nimgcenter', 'coord_type', coordTypeMap[coordType]);
query.set('nimgcenter', 'coord_type_conversion', 'yes');
if (andOr !== '') {
query.set('nimgcenter', 'nimgsearch_andor', andOr);
}
if (searchType !== 'all') {
query.set('nimgcenter', 'index_id', searchType === 'normal' ? '4375' : '4376');
}
if (baseItemIds.length > 0) {
query.set('nimgcenter', 'baseitem_id', baseItemIds.join(','));
}
ItemUtil.getListByAdvancedSearchQuery(query, sortCondition, func);
};
if (!advancedQeury.empty()) {
ItemUtil.getListByAdvancedSearchQuery(advancedQeury, null, (result) => {
const baseItemIds = result.data.map((item) => item.item_id);
searchMain(baseItemIds);
});
} else {
searchMain([]);
}
};
const NimgsearchUtil = {
fixBrainCoordinate,
roundBrainCoordinate,
getBrainCoordinateFromOffset,
updateBrainImage,
getBrainAreaNames,
getBrainAreaName,
getBrainAreaId,
searchXoonipsItems,
fixBrainCoordinate,
roundBrainCoordinate,
getBrainCoordinateFromOffset,
updateBrainImage,
getBrainAreaNames,
getBrainAreaName,
getBrainAreaId,
searchXoonipsItems,
};
export default NimgsearchUtil;
-1
View File
@@ -1 +0,0 @@
/// <reference types="react-scripts" />
-15
View File
@@ -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;
-5
View File
@@ -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';
+26 -26
View File
@@ -1,70 +1,70 @@
.database {
margin: 0;
margin: 0;
}
.database :global(.list) {
width: 100%;
width: 100%;
}
.database :global(.listTable) {
width: 100%;
border-collapse: separate;
border-spacing: 5px;
width: 100%;
border-collapse: separate;
border-spacing: 5px;
}
.database :global(.listTable .listIcon),
.database :global(.listTable .listExtra) {
vertical-align: middle;
text-align: center;
width: 65px;
line-height: 0;
vertical-align: middle;
text-align: center;
width: 65px;
line-height: 0;
}
.database :global(.itemDetail) {
border-collapse: separate;
border-spacing: 1px;
border-collapse: separate;
border-spacing: 1px;
}
.database :global(.itemDetail .head) {
width: 30%;
width: 30%;
}
.database :global(.itemDetail .readme),
.database :global(.itemDetail .rights) {
background-color: #ffffff;
width: 100%;
max-height: 200px;
overflow: auto;
background-color: #ffffff;
width: 100%;
max-height: 200px;
overflow: auto;
}
.database :global(.advancedSearch .head) {
width: 30%;
width: 30%;
}
.database :global(.advancedSearch .search) {
text-align: center;
margin: 10px;
text-align: center;
margin: 10px;
}
.database :global(.advancedSearch .itemtype) {
margin-bottom: 5px;
margin-bottom: 5px;
}
.database :global(.advancedSearch .itemtype .itemtypeName),
.database :global(.advancedSearch .itemtype .itemtypeFields) {
border-collapse: separate;
border-spacing: 1px;
border-collapse: separate;
border-spacing: 1px;
}
.database :global(.advancedSearch .itemtype .itemtypeName th) {
padding: 5px;
padding: 5px;
}
.database :global(.advancedSearch .fieldDateLabel) {
display: inline-block;
width: 50px;
display: inline-block;
width: 50px;
}
.database :global(.advancedSearch .fieldDate select),
.database :global(.advancedSearch .fieldDate input) {
margin: 0 5px 0 0;
margin: 0 5px 0 0;
}
+99 -99
View File
@@ -1,117 +1,117 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import { INDEX_ID_PUBLIC } from "./lib/IndexUtil";
import ItemUtil from "./lib/ItemUtil";
import styles from "./Xoonips.module.css";
import XoonipsAdvancedSearch from "./XoonipsAdvancedSearch";
import XoonipsDetailItem from "./XoonipsDetailItem";
import XoonipsSearchByAdvancedKeyword from "./XoonipsSearchByAdvancedKeyword";
import XoonipsSearchByIndexId from "./XoonipsSearchByIndexId";
import XoonipsSearchByItemType from "./XoonipsSearchByItemType";
import XoonipsSearchByKeyword from "./XoonipsSearchByKeyword";
import XoonipsTop from "./XoonipsTop";
import type React from 'react';
import { Helmet } from 'react-helmet-async';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
import Functions from '../functions';
import { INDEX_ID_PUBLIC } from './lib/IndexUtil';
import ItemUtil from './lib/ItemUtil';
import styles from './Xoonips.module.css';
import XoonipsAdvancedSearch from './XoonipsAdvancedSearch';
import XoonipsDetailItem from './XoonipsDetailItem';
import XoonipsSearchByAdvancedKeyword from './XoonipsSearchByAdvancedKeyword';
import XoonipsSearchByIndexId from './XoonipsSearchByIndexId';
import XoonipsSearchByItemType from './XoonipsSearchByItemType';
import XoonipsSearchByKeyword from './XoonipsSearchByKeyword';
import XoonipsTop from './XoonipsTop';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const ItemDetail: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const itemIdStr = params.get("item_id");
const itemId = itemIdStr !== null ? (/^\d+$/.test(itemIdStr) ? parseInt(itemIdStr, 10) : 0) : null;
const doi = params.get("id");
return <XoonipsDetailItem lang={lang} id={itemId} doi={doi} />;
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const itemIdStr = params.get('item_id');
const itemId = itemIdStr !== null ? (/^\d+$/.test(itemIdStr) ? parseInt(itemIdStr, 10) : 0) : null;
const doi = params.get('id');
return <XoonipsDetailItem lang={lang} id={itemId} doi={doi} />;
};
const ItemList: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const id = params.get("index_id") || "";
const indexId = /^\d+$/.test(id) ? parseInt(id, 10) : INDEX_ID_PUBLIC;
return <XoonipsSearchByIndexId lang={lang} id={indexId} />;
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const id = params.get('index_id') || '';
const indexId = /^\d+$/.test(id) ? parseInt(id, 10) : INDEX_ID_PUBLIC;
return <XoonipsSearchByIndexId lang={lang} id={indexId} />;
};
const ItemSelect: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const op = params.get("op") ?? "";
switch (op) {
case "quicksearch": {
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(location.search);
return <XoonipsSearchByKeyword lang={lang} type={type} keyword={keyword} />;
}
case "itemtypesearch": {
const searchItemtype = params.get("search_itemtype") || "";
const match = searchItemtype.match(/^xnp([a-z]+)$/);
const itemType = match !== null ? match[1] : "";
return <XoonipsSearchByItemType lang={lang} itemType={itemType} itemSubType="" />;
}
case "itemsubtypesearch": {
let itemType: string | null = null;
let itemSubType: string | null = null;
// xoonips 3.x compatibility style
params.forEach((v, k) => {
if (/^xnp[a-z]+$/.test(k) && v === "on") {
itemType = k.replace(/^xnp/, "");
return;
}
});
if (itemType !== null) {
params.forEach((v, k) => {
if (k.match(`^xnp${itemType}_.+$`) !== null && !!v) {
itemSubType = v;
return;
}
});
}
// simplified when archiving
if (itemType === null) {
const searchItemtype = params.get("search_itemtype") ?? "";
const match = searchItemtype.match(/^xnp([a-z]+)$/);
itemType = match !== null ? match[1] : "";
}
if (itemSubType === null) {
itemSubType = params.get("search_subitemtype") ?? "";
}
return <XoonipsSearchByItemType lang={lang} itemType={itemType} itemSubType={itemSubType} />;
}
case "advanced": {
return <XoonipsSearchByAdvancedKeyword lang={lang} />;
}
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const op = params.get('op') ?? '';
switch (op) {
case 'quicksearch': {
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(location.search);
return <XoonipsSearchByKeyword lang={lang} type={type} keyword={keyword} />;
}
return <PageNotFound lang={lang} />;
case 'itemtypesearch': {
const searchItemtype = params.get('search_itemtype') || '';
const match = searchItemtype.match(/^xnp([a-z]+)$/);
const itemType = match !== null ? match[1] : '';
return <XoonipsSearchByItemType lang={lang} itemType={itemType} itemSubType="" />;
}
case 'itemsubtypesearch': {
let itemType: string | null = null;
let itemSubType: string | null = null;
// xoonips 3.x compatibility style
params.forEach((v, k) => {
if (/^xnp[a-z]+$/.test(k) && v === 'on') {
itemType = k.replace(/^xnp/, '');
return;
}
});
if (itemType !== null) {
params.forEach((v, k) => {
if (k.match(`^xnp${itemType}_.+$`) !== null && v) {
itemSubType = v;
return;
}
});
}
// simplified when archiving
if (itemType === null) {
const searchItemtype = params.get('search_itemtype') ?? '';
const match = searchItemtype.match(/^xnp([a-z]+)$/);
itemType = match !== null ? match[1] : '';
}
if (itemSubType === null) {
itemSubType = params.get('search_subitemtype') ?? '';
}
return <XoonipsSearchByItemType lang={lang} itemType={itemType} itemSubType={itemSubType} />;
}
case 'advanced': {
return <XoonipsSearchByAdvancedKeyword lang={lang} />;
}
}
return <PageNotFound lang={lang} />;
};
const Xoonips: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div className={styles.database}>
<Helmet>
<title>
{Functions.mlang("[en]Database[/en][ja]データベース[/ja]", lang)} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<Routes>
<Route index element={<XoonipsTop lang={lang} />} />
<Route path="index.php" element={<Navigate to="/modules/xoonips/" />} />
<Route path="detail.php" element={<ItemDetail lang={lang} />} />
<Route path="listitem.php" element={<ItemList lang={lang} />} />
<Route path="itemselect.php" element={<ItemSelect lang={lang} />} />
<Route path="advanced_search.php" element={<XoonipsAdvancedSearch lang={lang} />} />
<Route path="search/advanced" element={<XoonipsSearchByAdvancedKeyword lang={lang} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
const { lang } = props;
return (
<div className={styles.database}>
<Helmet>
<title>
{Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<Routes>
<Route index element={<XoonipsTop lang={lang} />} />
<Route path="index.php" element={<Navigate to="/modules/xoonips/" />} />
<Route path="detail.php" element={<ItemDetail lang={lang} />} />
<Route path="listitem.php" element={<ItemList lang={lang} />} />
<Route path="itemselect.php" element={<ItemSelect lang={lang} />} />
<Route path="advanced_search.php" element={<XoonipsAdvancedSearch lang={lang} />} />
<Route path="search/advanced" element={<XoonipsSearchByAdvancedKeyword lang={lang} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
};
export default Xoonips;
+35 -35
View File
@@ -1,45 +1,45 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import Config, { MultiLang } from "../config";
import Functions from "../functions";
import ItemType from "./item-type";
import AdvancedSearchQuery from "./lib/AdvancedSearchQuery";
import ItemUtil from "./lib/ItemUtil";
import type React from 'react';
import { useNavigate } from 'react-router-dom';
import Config, { type MultiLang } from '../config';
import Functions from '../functions';
import ItemType from './item-type';
import AdvancedSearchQuery from './lib/AdvancedSearchQuery';
import ItemUtil from './lib/ItemUtil';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoonipsAdvancedSearch: React.FC<Props> = (props: Props) => {
const { lang } = props;
const query = new AdvancedSearchQuery();
const navigate = useNavigate();
const { lang } = props;
const query = new AdvancedSearchQuery();
const navigate = useNavigate();
const handleClickSearchButton = () => {
if (!query.empty()) {
const url = ItemUtil.getSearchByAdvancedKeywordsUrl(query);
navigate(url);
}
};
const handleClickSearchButton = () => {
if (!query.empty()) {
const url = ItemUtil.getSearchByAdvancedKeywordsUrl(query);
navigate(url);
}
};
return (
<div className="advancedSearch">
<h3>{Functions.mlang("[en]Search Items[/en][ja]アイテム検索[/ja]", lang)}</h3>
<div className="search">
<button className="formButton" onClick={handleClickSearchButton}>
Search
</button>
</div>
{Config.XOONIPS_ITEMTYPES.map((type) => {
return <ItemType.AdvancedSearch key={type} type={"xnp" + type} lang={lang} query={query} />;
})}
<div className="search">
<button className="formButton" onClick={handleClickSearchButton}>
Search
</button>
</div>
</div>
);
return (
<div className="advancedSearch">
<h3>{Functions.mlang('[en]Search Items[/en][ja]アイテム検索[/ja]', lang)}</h3>
<div className="search">
<button className="formButton" onClick={handleClickSearchButton}>
Search
</button>
</div>
{Config.XOONIPS_ITEMTYPES.map((type) => {
return <ItemType.AdvancedSearch key={type} type={'xnp' + type} lang={lang} query={query} />;
})}
<div className="search">
<button className="formButton" onClick={handleClickSearchButton}>
Search
</button>
</div>
</div>
);
};
export default XoonipsAdvancedSearch;
+47 -45
View File
@@ -1,56 +1,58 @@
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import Loading from "../common/lib/Loading";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import ItemType from "./item-type";
import ItemUtil, { Item } from "./lib/ItemUtil";
import type React from 'react';
import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import Loading from '../common/lib/Loading';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
import Functions from '../functions';
import ItemType from './item-type';
import ItemUtil, { type Item } from './lib/ItemUtil';
interface Props {
lang: MultiLang;
id: number | null;
doi: string | null;
lang: MultiLang;
id: number | null;
doi: string | null;
}
const XoonipsDetailItem: React.FC<Props> = (props: Props) => {
const { lang, id, doi } = props;
const { lang, id, doi } = props;
const [loading, setLoading] = useState<boolean>(true);
const [item, setItem] = useState<Item | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [item, setItem] = useState<Item | null>(null);
useEffect(() => {
if (doi !== null) {
ItemUtil.getByDoi(doi, (item) => {
setLoading(false);
setItem(item);
});
} else if (id !== null) {
ItemUtil.get(id, (item) => {
setLoading(false);
setItem(item);
});
}
}, [id, doi]);
if (loading) {
return <Loading />;
useEffect(() => {
if (doi !== null) {
ItemUtil.getByDoi(doi, (item) => {
setLoading(false);
setItem(item);
});
} else if (id !== null) {
ItemUtil.get(id, (item) => {
setLoading(false);
setItem(item);
});
}
if (item === null) {
return <PageNotFound lang={lang} />;
}
return (
<>
<Helmet>
<title>
{Functions.mlang(item.title, lang)} - {Functions.mlang("[en]Database[/en][ja]データベース[/ja]", lang)} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{Functions.mlang("[en]Detail[/en][ja]詳細[/ja]", lang)}</h3>
<br />
<ItemType.Detail lang={lang} item={item} />
</>
);
}, [id, doi]);
if (loading) {
return <Loading />;
}
if (item === null) {
return <PageNotFound lang={lang} />;
}
return (
<>
<Helmet>
<title>
{Functions.mlang(item.title, lang)} - {Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} -{' '}
{Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{Functions.mlang('[en]Detail[/en][ja]詳細[/ja]', lang)}</h3>
<br />
<ItemType.Detail lang={lang} item={item} />
</>
);
};
export default XoonipsDetailItem;
+26 -24
View File
@@ -1,35 +1,37 @@
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { MultiLang } from "../config";
import AdvancedSearchQuery from "./lib/AdvancedSearchQuery";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
import XoonipsListItem from "./lib/XoonipsListItem";
import type React from 'react';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import type { MultiLang } from '../config';
import type AdvancedSearchQuery from './lib/AdvancedSearchQuery';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoonipsSearchByAdvancedKeyword: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const [query, setQeury] = useState<AdvancedSearchQuery>(ItemUtil.getAdvancedSearchQueryByQuery(location.search));
const baseUrl = ItemUtil.getSearchByAdvancedKeywordsUrl(query);
const { lang } = props;
const location = useLocation();
const [query, setQeury] = useState<AdvancedSearchQuery>(ItemUtil.getAdvancedSearchQueryByQuery(location.search));
const baseUrl = ItemUtil.getSearchByAdvancedKeywordsUrl(query);
useEffect(() => {
const query = ItemUtil.getAdvancedSearchQueryByQuery(location.search);
setQeury(query);
}, [location]);
useEffect(() => {
const query = ItemUtil.getAdvancedSearchQueryByQuery(location.search);
// eslint-disable-next-line react-hooks/set-state-in-effect
setQeury(query);
}, [location]);
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
ItemUtil.getListByAdvancedSearchQuery(query, condition, func);
};
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
ItemUtil.getListByAdvancedSearchQuery(query, condition, func);
};
return (
<div className="list">
<h3>Listing item</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
return (
<div className="list">
<h3>Listing item</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
};
export default XoonipsSearchByAdvancedKeyword;
+54 -52
View File
@@ -1,66 +1,68 @@
import React, { Fragment } from "react";
import { Helmet } from "react-helmet-async";
import { Link } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import IndexUtil, { Index, INDEX_ID_PUBLIC } from "./lib/IndexUtil";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
import XoonipsListIndex from "./lib/XoonipsListIndex";
import XoonipsListItem from "./lib/XoonipsListItem";
import type React from 'react';
import { Fragment } from 'react';
import { Helmet } from 'react-helmet-async';
import { Link } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
import Functions from '../functions';
import IndexUtil, { INDEX_ID_PUBLIC, type Index } from './lib/IndexUtil';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListIndex from './lib/XoonipsListIndex';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
id: number | null;
lang: MultiLang;
id: number | null;
}
const XoonipsSearchByIndexId: React.FC<Props> = (props: Props) => {
const { lang, id } = props;
const indexId = id ?? INDEX_ID_PUBLIC;
const { lang, id } = props;
const indexId = id ?? INDEX_ID_PUBLIC;
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (indexId === 0) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByIndexId(indexId, condition, func);
}
};
const index = IndexUtil.get(indexId);
if (index === null) {
return <PageNotFound lang={lang} />;
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (indexId === 0) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByIndexId(indexId, condition, func);
}
};
const baseUrl = indexId === 0 ? "/" : IndexUtil.getUrl(indexId);
const pIndexes = IndexUtil.getParents(indexId);
const parents = pIndexes.map((value: Index) => {
const url: string = IndexUtil.getUrl(value.id);
return (
<Fragment key={value.id}>
/ <Link to={url}>{value.title}</Link>{" "}
</Fragment>
);
});
const title = pIndexes
.map((value) => {
return "/" + value.title;
})
.join("");
const index = IndexUtil.get(indexId);
if (index === null) {
return <PageNotFound lang={lang} />;
}
const baseUrl = indexId === 0 ? '/' : IndexUtil.getUrl(indexId);
const pIndexes = IndexUtil.getParents(indexId);
const parents = pIndexes.map((value: Index) => {
const url: string = IndexUtil.getUrl(value.id);
return (
<div className="list">
<Helmet>
<title>
{Functions.mlang(title, lang)} - {Functions.mlang("[en]Database[/en][ja]データベース[/ja]", lang)} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{Functions.mlang("[en]Listing item[/en][ja]アイテム一覧[/ja]", lang)}</h3>
<div>{parents}</div>
<XoonipsListIndex lang={lang} index={index} />
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
<Fragment key={value.id}>
/ <Link to={url}>{value.title}</Link>{' '}
</Fragment>
);
});
const title = pIndexes
.map((value) => {
return '/' + value.title;
})
.join('');
return (
<div className="list">
<Helmet>
<title>
{Functions.mlang(title, lang)} - {Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} -{' '}
{Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}</h3>
<div>{parents}</div>
<XoonipsListIndex lang={lang} index={index} />
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
};
export default XoonipsSearchByIndexId;
+24 -24
View File
@@ -1,34 +1,34 @@
import React from "react";
import { MultiLang } from "../config";
import Functions from "../functions";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
import XoonipsListItem from "./lib/XoonipsListItem";
import type React from 'react';
import type { MultiLang } from '../config';
import Functions from '../functions';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
itemType: string | null;
itemSubType: string | null;
lang: MultiLang;
itemType: string | null;
itemSubType: string | null;
}
const XoonipsSearchByItemType: React.FC<Props> = (props: Props) => {
const { lang, itemType, itemSubType } = props;
const { lang, itemType, itemSubType } = props;
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (itemType === null || itemSubType === null) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByItemType(itemType, itemSubType, condition, func);
}
};
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (itemType === null || itemSubType === null) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByItemType(itemType, itemSubType, condition, func);
}
};
const baseUrl = ItemUtil.getItemTypeSearchUrl(itemType, itemSubType);
return (
<div className="list">
<h3>{Functions.mlang("[en]Listing item[/en][ja]アイテム一覧[/ja]", lang)}</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
const baseUrl = ItemUtil.getItemTypeSearchUrl(itemType, itemSubType);
return (
<div className="list">
<h3>{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
};
export default XoonipsSearchByItemType;
+27 -27
View File
@@ -1,37 +1,37 @@
import React from "react";
import { MultiLang } from "../config";
import Functions from "../functions";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
import XoonipsListItem from "./lib/XoonipsListItem";
import type React from 'react';
import type { MultiLang } from '../config';
import Functions from '../functions';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
type: string;
keyword: string;
lang: MultiLang;
type: string;
keyword: string;
}
const XoonipsSearchByKeyword: React.FC<Props> = (props: Props) => {
const { lang, type, keyword } = props;
const { lang, type, keyword } = props;
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (keyword === "") {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByKeyword(type, keyword, condition, func);
}
};
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (keyword === '') {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByKeyword(type, keyword, condition, func);
}
};
const baseUrl = ItemUtil.getSearchByKeywordUrl(type, keyword);
return (
<div className="list">
<h3>{Functions.mlang("[en]Listing item[/en][ja]アイテム一覧[/ja]", lang)}</h3>
<p>
{Functions.mlang("[en]Search Keyword[/en][ja]検索キーワード[/ja]", lang)} : {keyword}
</p>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
const baseUrl = ItemUtil.getSearchByKeywordUrl(type, keyword);
return (
<div className="list">
<h3>{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}</h3>
<p>
{Functions.mlang('[en]Search Keyword[/en][ja]検索キーワード[/ja]', lang)} : {keyword}
</p>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} />
</div>
);
};
export default XoonipsSearchByKeyword;
+5 -5
View File
@@ -1,13 +1,13 @@
.itemTypes .itemType {
width: 50%;
padding: 5px;
width: 50%;
padding: 5px;
}
.itemTypes .itemType :global(table) {
width: auto;
width: auto;
}
.itemTypes .itemType :global(table .itemTypeName) {
vertical-align: middle;
font-size: large;
vertical-align: middle;
font-size: large;
}
+32 -32
View File
@@ -1,40 +1,40 @@
import React from "react";
import Config, { MultiLang } from "../config";
import ItemType from "./item-type";
import styles from "./XoonipsTop.module.css";
import type React from 'react';
import Config, { type MultiLang } from '../config';
import ItemType from './item-type';
import styles from './XoonipsTop.module.css';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoonipsTop: React.FC<Props> = (props: Props) => {
const { lang } = props;
const types: string[][] = [];
const len = Config.XOONIPS_ITEMTYPES.length;
for (let i = 0; i < Math.ceil(len / 2); i++) {
const j = i * 2;
const p = Config.XOONIPS_ITEMTYPES.slice(j, j + 2);
types.push(p);
}
return (
<table className={styles.itemTypes}>
<tbody>
{types.map((value, idx) => {
return (
<tr key={idx}>
{value.map((type, idx) => {
return (
<td key={idx} className={styles.itemType}>
{type !== "" && <ItemType.Top lang={lang} type={"xnp" + type} />}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
const { lang } = props;
const types: string[][] = [];
const len = Config.XOONIPS_ITEMTYPES.length;
for (let i = 0; i < Math.ceil(len / 2); i++) {
const j = i * 2;
const p = Config.XOONIPS_ITEMTYPES.slice(j, j + 2);
types.push(p);
}
return (
<table className={styles.itemTypes}>
<tbody>
{types.map((value, idx) => {
return (
<tr key={idx}>
{value.map((type, idx) => {
return (
<td key={idx} className={styles.itemType}>
{type !== '' && <ItemType.Top lang={lang} type={'xnp' + type} />}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
};
export default XoonipsTop;
+27 -27
View File
@@ -1,36 +1,36 @@
import { Navigate, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import { Navigate, useLocation } from 'react-router-dom';
import PageNotFound from '../common/lib/PageNotFound';
import type { MultiLang } from '../config';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoonipsXoopsPathRedirect: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const getRedirectUrl = () => {
const pathname = location.pathname || "";
// const query = new URLSearchParams(location.search);
const search = new RegExp("^/modules/xoonips(?:/+(.*))?$");
const matches = pathname.match(search);
if (matches === null) {
return "";
}
const path = matches[1] || "";
switch (path) {
case "":
case "index.php": {
return "/modules/xoonips";
}
}
return "";
};
const url = getRedirectUrl();
if (url === "") {
return <PageNotFound lang={lang} />;
const { lang } = props;
const location = useLocation();
const getRedirectUrl = () => {
const pathname = location.pathname || '';
// const query = new URLSearchParams(location.search);
const search = /^\/modules\/xoonips(?:\/+(.*))?$/;
const matches = pathname.match(search);
if (matches === null) {
return '';
}
return <Navigate to={url} />;
const path = matches[1] || '';
switch (path) {
case '':
case 'index.php': {
return '/modules/xoonips';
}
}
return '';
};
const url = getRedirectUrl();
if (url === '') {
return <PageNotFound lang={lang} />;
}
return <Navigate to={url} />;
};
export default XoonipsXoopsPathRedirect;
+1 -1
View File
@@ -1 +1 @@
[]
[]
File diff suppressed because one or more lines are too long
+53 -48
View File
@@ -1,91 +1,96 @@
.indexTree {
border-top: 1px solid #999999;
border-left: 1px solid #999999;
border-bottom: 1px solid #404040;
border-right: 1px solid #404040;
background-color: white;
height: 400px;
width: calc(100% - 6px);
overflow: auto;
margin: 0 auto;
padding: 3px;
line-height: 100%;
border-top: 1px solid #999999;
border-left: 1px solid #999999;
border-bottom: 1px solid #404040;
border-right: 1px solid #404040;
background-color: white;
height: 400px;
width: calc(100% - 6px);
overflow: auto;
margin: 0 auto;
padding: 3px;
line-height: 100%;
}
.formButton {
margin: 3px 3px 10px;
margin: 3px 3px 10px;
}
.indexTree div span {
color: #333;
font-size: 12px;
font-weight: bold;
line-height: 19px;
vertical-align: bottom;
color: #333;
font-size: 12px;
font-weight: bold;
line-height: 19px;
vertical-align: bottom;
}
.indexTree div span:hover {
color: #f60;
color: #f60;
}
.indexTree:global(.rc-tree .rc-tree-treenode) {
line-height: 20px;
white-space: nowrap;
line-height: 20px;
white-space: nowrap;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent) {
display: inline-block;
display: inline-block;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit) {
display: inline-block;
height: 20px;
width: 9px;
background: url(../assets/images/tree_line.png);
display: inline-block;
height: 20px;
width: 9px;
background: url(../assets/images/tree_line.png);
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:not(:last-child)) {
background-position: -9px 0;
background-position: -9px 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:not(:last-child).rc-tree-indent-unit-end) {
background-position: -18px 0;
.indexTree:global(
.rc-tree
.rc-tree-treenode
.rc-tree-indent
.rc-tree-indent-unit:not(:last-child).rc-tree-indent-unit-end
) {
background-position: -18px 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:last-child.rc-tree-indent-unit-end) {
background-position: -27px 0;
background-position: -27px 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-switcher) {
display: inline-block;
height: 20px;
width: 16px;
margin-right: 2px;
background: url(../assets/images/tree_node.png);
cursor: pointer;
display: inline-block;
height: 20px;
width: 16px;
margin-right: 2px;
background: url(../assets/images/tree_node.png);
cursor: pointer;
}
.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-switcher.rc-tree-switcher-noop) {
background-position: 0 0;
cursor: auto;
background-position: 0 0;
cursor: auto;
}
.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-switcher.rc-tree-switcher_open) {
background-position: -16px 0;
background-position: -16px 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-switcher.rc-tree-switcher_close) {
background-position: -32px 0;
background-position: -32px 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher-noop) {
background-position: 0 -20px;
cursor: auto;
background-position: 0 -20px;
cursor: auto;
}
.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher_open) {
background-position: -16px -20px;
background-position: -16px -20px;
}
.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher_close) {
background-position: -32px -20px;
background-position: -32px -20px;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-node-content-wrapper) {
display: inline-block;
height: 20px;
cursor: pointer;
display: inline-block;
height: 20px;
cursor: pointer;
}
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-node-content-wrapper .rc-tree-title) {
vertical-align: middle;
line-height: 20px;
vertical-align: middle;
line-height: 20px;
}
+105 -95
View File
@@ -1,114 +1,124 @@
import Tree from "rc-tree";
import { DataNode, EventDataNode } from "rc-tree/lib/interface";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import IndexUtil, { Index, INDEX_ID_PUBLIC } from "../lib/IndexUtil";
import styles from "./IndexTree.module.css";
import Tree from 'rc-tree';
import type { DataNode, EventDataNode } from 'rc-tree/lib/interface';
import type React from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import IndexUtil, { INDEX_ID_PUBLIC, type Index } from '../lib/IndexUtil';
import styles from './IndexTree.module.css';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
type Key = string | number;
type Key = React.Key;
const initialize = (lang: MultiLang) => {
let keys: string[] = [];
let eKeys: string[] = [];
const makeTreeNode = (index: Index, depth: number): DataNode => {
const title = Functions.mlang(index.title, lang) + (index.numOfItems > 0 ? " (" + index.numOfItems + ")" : "");
const children = IndexUtil.getChildren(index.id);
if (children.length === 0) {
return { key: String(index.id), title: title };
}
const childTreeNodes = children.map((value: Index) => {
return makeTreeNode(value, depth + 1);
});
if (depth < 1) {
eKeys.push(String(index.id));
}
keys.push(String(index.id));
return { key: String(index.id), title: title, children: childTreeNodes };
};
let elements: DataNode[] = [];
const index = IndexUtil.get(INDEX_ID_PUBLIC);
if (index !== null) {
elements.push(makeTreeNode(index, 0));
const keys: string[] = [];
const eKeys: string[] = [];
const makeTreeNode = (index: Index, depth: number): DataNode => {
const title = Functions.mlang(index.title, lang) + (index.numOfItems > 0 ? ' (' + index.numOfItems + ')' : '');
const children = IndexUtil.getChildren(index.id);
if (children.length === 0) {
return { key: String(index.id), title: title };
}
return {
elements: elements,
keys: keys,
expandedKeys: eKeys,
};
const childTreeNodes = children.map((value: Index) => {
return makeTreeNode(value, depth + 1);
});
if (depth < 1) {
eKeys.push(String(index.id));
}
keys.push(String(index.id));
return { key: String(index.id), title: title, children: childTreeNodes };
};
const elements: DataNode[] = [];
const index = IndexUtil.get(INDEX_ID_PUBLIC);
if (index !== null) {
elements.push(makeTreeNode(index, 0));
}
return {
elements: elements,
keys: keys,
expandedKeys: eKeys,
};
};
const IndexTree: React.FC<Props> = (props: Props) => {
const { lang } = props;
const { lang } = props;
const res = initialize(lang);
const navigate = useNavigate();
const [tree, setTree] = useState<DataNode[]>(res.elements);
const [expandableKeys, setExpandableKeys] = useState<string[]>(res.keys);
const [expandedKeys, setExpandedKeys] = useState<string[]>(res.expandedKeys);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
useEffect(() => {
const res = initialize(lang);
const navigate = useNavigate();
const [tree, setTree] = useState<DataNode[]>(res.elements);
const [expandableKeys, setExpandableKeys] = useState<string[]>(res.keys);
const [expandedKeys, setExpandedKeys] = useState<string[]>(res.expandedKeys);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
// eslint-disable-next-line react-hooks/set-state-in-effect
setTree(res.elements);
setExpandableKeys(res.keys);
setSelectedKeys([]);
}, [lang]);
useEffect(() => {
const res = initialize(lang);
setTree(res.elements);
setExpandableKeys(res.keys);
setSelectedKeys([]);
}, [lang]);
const handleClickOpenAll = () => {
setExpandedKeys(expandableKeys);
};
const handleClickOpenAll = () => {
setExpandedKeys(expandableKeys);
};
const handleClickCloseAll = () => {
setExpandedKeys([]);
};
const handleClickCloseAll = () => {
setExpandedKeys([]);
};
const handleExpand = (
expandedKeys: Key[],
_info: {
node: EventDataNode<DataNode>;
expanded: boolean;
nativeEvent: MouseEvent;
},
): void => {
const keys: string[] = expandedKeys.map((key) => {
return typeof key === 'string' ? key : String(key);
});
setExpandedKeys(keys);
};
const handleExpand = (
expandedKeys: Key[],
info: {
node: EventDataNode<DataNode>;
expanded: boolean;
nativeEvent: MouseEvent;
}
): void => {
const keys: string[] = expandedKeys.map((key) => {
return typeof key === "string" ? key : String(key);
});
setExpandedKeys(keys);
};
const handleSelect = (
selectedKeys: Key[],
_info: {
event: 'select';
selected: boolean;
node: EventDataNode<DataNode>;
selectedNodes: DataNode[];
nativeEvent: MouseEvent;
},
): void => {
const selectedKey = selectedKeys.shift() || 0;
const key = typeof selectedKey === 'string' ? parseInt(selectedKey, 10) : Number(selectedKey);
const url = IndexUtil.getUrl(key);
setSelectedKeys([]);
navigate(url);
};
const handleSelect = (
selectedKeys: Key[],
info: {
event: "select";
selected: boolean;
node: EventDataNode<DataNode>;
selectedNodes: DataNode[];
nativeEvent: MouseEvent;
}
): void => {
const selectedKey = selectedKeys.shift() || 0;
const key = typeof selectedKey === "string" ? parseInt(selectedKey, 10) : selectedKey;
const url = IndexUtil.getUrl(key);
setSelectedKeys([]);
navigate(url);
};
return (
<div className={styles.container}>
<button className={styles.formButton} onClick={handleClickOpenAll}>
open all
</button>
<button className={styles.formButton} onClick={handleClickCloseAll}>
close all
</button>
<Tree className={styles.indexTree} expandedKeys={expandedKeys} selectedKeys={selectedKeys} onExpand={handleExpand} onSelect={handleSelect} showIcon={false} treeData={tree} />
</div>
);
return (
<div className={styles.container}>
<button className={styles.formButton} onClick={handleClickOpenAll}>
open all
</button>
<button className={styles.formButton} onClick={handleClickCloseAll}>
close all
</button>
<Tree
className={styles.indexTree}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
onExpand={handleExpand}
onSelect={handleSelect}
showIcon={false}
treeData={tree}
/>
</div>
);
};
export default IndexTree;
+11 -11
View File
@@ -1,28 +1,28 @@
.heading {
margin: 0 0 3px;
font-size: 100%;
font-weight: normal;
margin: 0 0 3px;
font-size: 100%;
font-weight: normal;
}
.item {
display: flex;
white-space: nowrap;
width: 100%;
display: flex;
white-space: nowrap;
width: 100%;
}
.order {
margin-right: 5px;
margin-right: 5px;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
text-overflow: ellipsis;
}
.count {
margin-left: 10px;
margin-left: 10px;
}
.star {
margin-left: 5px;
margin-left: 5px;
}
+168 -168
View File
@@ -1,183 +1,183 @@
import axios from "axios";
import React, { Fragment, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Loading from "../../common/lib/Loading";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import imageStar from "../assets/images/star.gif";
import ItemUtil, { ItemCore } from "../lib/ItemUtil";
import styles from "./Rankings.module.css";
import ky from 'ky';
import type React from 'react';
import { Fragment, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import Loading from '../../common/lib/Loading';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import imageStar from '../assets/images/star.gif';
import ItemUtil, { type ItemCore } from '../lib/ItemUtil';
import styles from './Rankings.module.css';
interface RankingsData {
accessed?: {
item_id: number;
doi: string;
title: string;
count: number;
}[];
downloaded?: {
item_id: number;
doi: string;
title: string;
count: number;
}[];
contributed?: {
uid: number;
uname: string;
name: string;
count: number;
}[];
searched?: {
keyword: string;
count: number;
}[];
accessed?: {
item_id: number;
doi: string;
title: string;
count: number;
}[];
downloaded?: {
item_id: number;
doi: string;
title: string;
count: number;
}[];
contributed?: {
uid: number;
uname: string;
name: string;
count: number;
}[];
searched?: {
keyword: string;
count: number;
}[];
}
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const Rankings: React.FC<Props> = (props: Props) => {
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [rankings, setRankings] = useState<RankingsData | null>(null);
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [rankings, setRankings] = useState<RankingsData | null>(null);
useEffect(() => {
const updatePage = async () => {
try {
const response = await axios.get("/modules/xoonips/rankings.json");
const rankings = response.status === 200 ? (response.data as RankingsData) : null;
setRankings(rankings);
} catch (e) {
setRankings(null);
}
setLoading(false);
};
updatePage();
}, []);
const orderLabel = (order: number, lang: string) => {
return String(order) + (lang === "en" ? Functions.ordinal(order) : "位");
useEffect(() => {
const updatePage = async () => {
try {
const rankings = (await ky.get('/modules/xoonips/rankings.json').json()) as RankingsData;
setRankings(rankings);
} catch (_e) {
setRankings(null);
}
setLoading(false);
};
updatePage();
}, []);
if (loading) {
return <Loading />;
}
if (rankings === null) {
return <div>Failed to get data resource.</div>;
}
const orderLabel = (order: number, lang: string) => {
return String(order) + (lang === 'en' ? Functions.ordinal(order) : '位');
};
const items = [];
if (rankings.accessed) {
items.push({
title: "[en]Frequently accessed items[/en][ja]頻繁に閲覧されるアイテム[/ja]",
list: rankings.accessed.map((item, idx) => {
const url = ItemUtil.getUrl(item as ItemCore);
const title = Functions.mlang(item.title, lang);
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link to={url} title={title}>
{title}
</Link>
</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
if (rankings.downloaded) {
items.push({
title: "[en]Frequently downloaded items[/en][ja]頻繁にダウンロードされるアイテム[/ja]",
list: rankings.downloaded.map((item, idx) => {
const url = ItemUtil.getUrl(item as ItemCore);
const title = Functions.mlang(item.title, lang);
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link to={url} title={title}>
{title}
</Link>
</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
if (rankings.contributed) {
items.push({
title: "[en]Most active contributers[/en][ja]最も多くアイテムを公開したユーザ[/ja]",
list: rankings.contributed.map((item, idx) => {
const name = item.name !== "" ? item.name + " (" + item.uname + ")" : item.uname;
const title = Functions.mlang(name, lang);
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.uname}>{title}</div>
<div className={styles.count}>({item.count})</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
if (rankings.searched) {
items.push({
title: "[en]Frequently searched keywords[/en][ja]頻繁に検索されるキーワード[/ja]",
list: rankings.searched.map((item, idx) => {
const url = ItemUtil.getSearchByKeywordUrl("all", item.keyword);
const title = "Search by: " + item.keyword;
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link to={url} title={title}>
{item.keyword}
</Link>
</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
return (
<div>
{items.map((item, idx) => {
return (
<Fragment key={idx}>
{idx !== 0 && <hr />}
<h3 className={styles.heading}>{Functions.mlang(item.title, lang)}</h3>
<div>{item.list}</div>
</Fragment>
);
})}
</div>
);
if (loading) {
return <Loading />;
}
if (rankings === null) {
return <div>Failed to get data resource.</div>;
}
const items = [];
if (rankings.accessed) {
items.push({
title: '[en]Frequently accessed items[/en][ja]頻繁に閲覧されるアイテム[/ja]',
list: rankings.accessed.map((item, idx) => {
const url = ItemUtil.getUrl(item as ItemCore);
const title = Functions.mlang(item.title, lang);
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link to={url} title={title}>
{title}
</Link>
</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
if (rankings.downloaded) {
items.push({
title: '[en]Frequently downloaded items[/en][ja]頻繁にダウンロードされるアイテム[/ja]',
list: rankings.downloaded.map((item, idx) => {
const url = ItemUtil.getUrl(item as ItemCore);
const title = Functions.mlang(item.title, lang);
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link to={url} title={title}>
{title}
</Link>
</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
if (rankings.contributed) {
items.push({
title: '[en]Most active contributers[/en][ja]最も多くアイテムを公開したユーザ[/ja]',
list: rankings.contributed.map((item, idx) => {
const name = item.name !== '' ? item.name + ' (' + item.uname + ')' : item.uname;
const title = Functions.mlang(name, lang);
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.uname}>{title}</div>
<div className={styles.count}>({item.count})</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
if (rankings.searched) {
items.push({
title: '[en]Frequently searched keywords[/en][ja]頻繁に検索されるキーワード[/ja]',
list: rankings.searched.map((item, idx) => {
const url = ItemUtil.getSearchByKeywordUrl('all', item.keyword);
const title = 'Search by: ' + item.keyword;
const order = orderLabel(idx + 1, lang);
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link to={url} title={title}>
{item.keyword}
</Link>
</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
}),
});
}
return (
<div>
{items.map((item, idx) => {
return (
<Fragment key={idx}>
{idx !== 0 && <hr />}
<h3 className={styles.heading}>{Functions.mlang(item.title, lang)}</h3>
<div>{item.list}</div>
</Fragment>
);
})}
</div>
);
};
export default Rankings;
+11 -11
View File
@@ -1,28 +1,28 @@
.heading {
margin: 0 0 3px;
font-size: 100%;
font-weight: normal;
margin: 0 0 3px;
font-size: 100%;
font-weight: normal;
}
.item {
display: flex;
white-space: nowrap;
width: 100%;
display: flex;
white-space: nowrap;
width: 100%;
}
.order {
margin-right: 5px;
margin-right: 5px;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
text-overflow: ellipsis;
}
.date {
margin-left: 10px;
margin-left: 10px;
}
.star {
margin-left: 5px;
margin-left: 5px;
}
+62 -62
View File
@@ -1,80 +1,80 @@
import axios from "axios";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Loading from "../../common/lib/Loading";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import imageStar from "../assets/images/star.gif";
import ItemUtil, { ItemCore } from "../lib/ItemUtil";
import styles from "./RecentContents.module.css";
import ky from 'ky';
import moment from 'moment';
import type React from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import Loading from '../../common/lib/Loading';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import imageStar from '../assets/images/star.gif';
import ItemUtil, { type ItemCore } from '../lib/ItemUtil';
import styles from './RecentContents.module.css';
interface RecentContentData {
item_id: number;
doi: string;
last_update_date: number;
title: string;
item_id: number;
doi: string;
last_update_date: number;
title: string;
}
type RecentContentsData = RecentContentData[];
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const RecentContents: React.FC<Props> = (props: Props) => {
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [contents, setContents] = useState<RecentContentsData | null>(null);
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [contents, setContents] = useState<RecentContentsData | null>(null);
useEffect(() => {
const updatePage = async () => {
try {
const response = await axios.get("/modules/xoonips/recent-contents.json");
const contents = response.status === 200 ? (response.data as RecentContentsData) : null;
setContents(contents);
} catch (e) {
setContents(null);
}
setLoading(false);
};
updatePage();
}, []);
useEffect(() => {
const updatePage = async () => {
try {
const contents = (await ky.get('/modules/xoonips/recent-contents.json').json()) as RecentContentsData;
setContents(contents);
} catch (_e) {
setContents(null);
}
setLoading(false);
};
updatePage();
}, []);
if (loading) {
return <Loading />;
}
if (contents === null) {
return <div>Failed to get data resource.</div>;
}
if (loading) {
return <Loading />;
}
if (contents === null) {
return <div>Failed to get data resource.</div>;
}
const list = contents.map((item, idx) => {
const url = ItemUtil.getUrl(item as ItemCore);
const title = Functions.mlang(item.title, lang);
const date = moment(new Date(item.last_update_date * 1000)).format("Y/M/D");
const order = String(idx + 1) + (lang === "en" ? Functions.ordinal(idx + 1) : "位");
return (
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link className={styles.title} to={url} title={title}>
{title}
</Link>
</div>
<div className={styles.date}> ({date})</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
});
const list = contents.map((item, idx) => {
const url = ItemUtil.getUrl(item as ItemCore);
const title = Functions.mlang(item.title, lang);
const date = moment(new Date(item.last_update_date * 1000)).format('Y/M/D');
const order = String(idx + 1) + (lang === 'en' ? Functions.ordinal(idx + 1) : '位');
return (
<div>
<h3 className={styles.heading}>{Functions.mlang("[en]Newly Arrived Items[/en][ja]新着アイテム[/ja]", lang)}</h3>
<div>{list}</div>
<div key={idx} className={styles.item}>
<div className={styles.order}>{order}</div>
<div className={styles.title}>
<Link className={styles.title} to={url} title={title}>
{title}
</Link>
</div>
<div className={styles.date}> ({date})</div>
{idx === 0 && (
<div className={styles.star}>
<img src={imageStar} alt={order} />
</div>
)}
</div>
);
});
return (
<div>
<h3 className={styles.heading}>{Functions.mlang('[en]Newly Arrived Items[/en][ja]新着アイテム[/ja]', lang)}</h3>
<div>{list}</div>
</div>
);
};
export default RecentContents;
+64 -60
View File
@@ -1,73 +1,77 @@
import React, { ChangeEvent, FormEvent, useEffect, useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import Config, { MultiLang } from "../../config";
import Functions from "../../functions";
import ItemUtil from "../lib/ItemUtil";
import type React from 'react';
import { type ChangeEvent, type FormEvent, useEffect, useState } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import Config, { type MultiLang } from '../../config';
import Functions from '../../functions';
import ItemUtil from '../lib/ItemUtil';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const Search: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const navigate = useNavigate();
const [type, setType] = useState("all");
const [keyword, setKeyword] = useState("");
const [pathname, setPathname] = useState(location.pathname);
const { lang } = props;
const location = useLocation();
const navigate = useNavigate();
const [type, setType] = useState('all');
const [keyword, setKeyword] = useState('');
const [pathname, setPathname] = useState(location.pathname);
const options = [
{ value: "all", label: "[en]All[/en][ja]全て[/ja]" },
{ value: "basic", label: "[en]Title & Keyword[/en][ja]タイトル&キーワード[/ja]" },
];
Config.XOONIPS_ITEMTYPES.forEach((type) => {
options.push({ value: `xnp${type}`, label: Functions.pascalCase(type) });
});
const options = [
{ value: 'all', label: '[en]All[/en][ja]全て[/ja]' },
{ value: 'basic', label: '[en]Title & Keyword[/en][ja]タイトル&キーワード[/ja]' },
];
Config.XOONIPS_ITEMTYPES.forEach((type) => {
options.push({ value: `xnp${type}`, label: Functions.pascalCase(type) });
});
useEffect(() => {
const pathnameNext = location.pathname;
if (pathname !== pathnameNext) {
if (pathnameNext === "/modules/xoonips/itemselect.php") {
const query = ItemUtil.getSearchKeywordByQuery(location.search);
setKeyword(query.keyword);
setType(query.type);
setPathname(pathnameNext);
}
}
}, [location, pathname]);
useEffect(() => {
const pathnameNext = location.pathname;
if (pathname !== pathnameNext) {
if (pathnameNext === '/modules/xoonips/itemselect.php') {
const query = ItemUtil.getSearchKeywordByQuery(location.search);
// eslint-disable-next-line react-hooks/set-state-in-effect
setKeyword(query.keyword);
setType(query.type);
setPathname(pathnameNext);
}
}
}, [location, pathname]);
const handleChangeKeyword = (event: ChangeEvent<HTMLInputElement>) => {
setKeyword(event.target.value.trim());
};
const handleChangeKeyword = (event: ChangeEvent<HTMLInputElement>) => {
setKeyword(event.target.value.trim());
};
const handleChangeType = (event: ChangeEvent<HTMLSelectElement>) => {
setType(event.target.value);
};
const handleChangeType = (event: ChangeEvent<HTMLSelectElement>) => {
setType(event.target.value);
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const url = ItemUtil.getSearchByKeywordUrl(type, keyword);
navigate(url);
};
return (
<form onSubmit={handleSubmit}>
<input style={{ width: "170px" }} type="text" value={keyword} onChange={handleChangeKeyword} />
&nbsp;&nbsp;
<select value={type} onChange={handleChangeType}>
{options.map((option) => {
return (
<option key={option.value} value={option.value}>
{Functions.mlang(option.label, lang)}
</option>
);
})}
</select>
<br />
<input className="formButton" type="submit" value="Search" />
&nbsp;&nbsp;&nbsp;&nbsp;
<Link to="/modules/xoonips/advanced_search.php">{Functions.mlang("[en]Advanced[/en][ja]詳細検索[/ja]", lang)}</Link>
</form>
);
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const url = ItemUtil.getSearchByKeywordUrl(type, keyword);
navigate(url);
};
return (
<form onSubmit={handleSubmit}>
<input style={{ width: '170px' }} type="text" value={keyword} onChange={handleChangeKeyword} />
&nbsp;&nbsp;
<select value={type} onChange={handleChangeType}>
{options.map((option) => {
return (
<option key={option.value} value={option.value}>
{Functions.mlang(option.label, lang)}
</option>
);
})}
</select>
<br />
<input className="formButton" type="submit" value="Search" />
&nbsp;&nbsp;&nbsp;&nbsp;
<Link to="/modules/xoonips/advanced_search.php">
{Functions.mlang('[en]Advanced[/en][ja]詳細検索[/ja]', lang)}
</Link>
</form>
);
};
export default Search;
@@ -1,25 +1,33 @@
import AdvancedSearchBase, { AdvancedSearchBaseProps } from "../lib/AdvancedSearchBase";
import { useCallback } from 'react';
import AdvancedSearchBase, { type AdvancedSearchBaseProps, type GetRowsContext } from '../lib/AdvancedSearchBase';
class BinderAdvancedSearch extends AdvancedSearchBase {
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = "binder";
this.title = "Binder";
this.state.values["title"] = "";
this.state.values["keyword"] = "";
this.state.values["description"] = "";
this.state.values["doi"] = "";
}
const BinderAdvancedSearch = (props: AdvancedSearchBaseProps) => {
const initialValues: Record<string, string> = {
title: '',
keyword: '',
description: '',
doi: '',
};
getRows() {
const rows = [
{ label: "[en]Title[/en][ja]タイトル[/ja]", value: this.renderFieldInputText("title", 50) },
{ label: "[en]Free Keywords[/en][ja]フリーキーワード[/ja]", value: this.renderFieldInputText("keyword", 50) },
{ label: "[en]Description[/en][ja]概要[/ja]", value: this.renderFieldInputText("description", 50) },
{ label: "ID", value: this.renderFieldInputText("doi", 50) },
];
return rows;
}
}
const getRows = useCallback((ctx: GetRowsContext) => {
return [
{ label: '[en]Title[/en][ja]タイトル[/ja]', value: ctx.renderFieldInputText('title', 50) },
{ label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: ctx.renderFieldInputText('keyword', 50) },
{ label: '[en]Description[/en][ja]概要[/ja]', value: ctx.renderFieldInputText('description', 50) },
{ label: 'ID', value: ctx.renderFieldInputText('doi', 50) },
];
}, []);
return (
<AdvancedSearchBase
{...props}
type="binder"
title="Binder"
initialValues={initialValues}
initialShow={false}
getRows={getRows}
/>
);
};
export default BinderAdvancedSearch;
+78 -94
View File
@@ -1,100 +1,84 @@
import React from "react";
import ItemType from "..";
import { MultiLang } from "../../../config";
import Functions from "../../../functions";
import ItemUtil, { Item, ItemBinder } from "../../lib/ItemUtil";
import DetailBase from "../lib/DetailBase";
import ItemTypeField from "../lib/field";
import type React from 'react';
import { useEffect, useState } from 'react';
import type { MultiLang } from '../../../config';
import Functions from '../../../functions';
import ItemUtil, { type Item, type ItemBinder } from '../../lib/ItemUtil';
import ItemType from '..';
import DetailBase, { type DetailBaseProps } from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
interface Props {
lang: MultiLang;
item: ItemBinder;
}
const BinderLinkItems: React.FC<{ lang: MultiLang; item: ItemBinder }> = ({ lang, item }) => {
const [items, setItems] = useState<Item[]>([]);
interface State {
items: Item[];
}
useEffect(() => {
const itemIds = item.item_link;
ItemUtil.getList(itemIds, (results) => {
setItems(results.data);
});
}, [item.item_link]);
class BinderLinkItems extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
items: [],
};
}
return (
<table className="listTable">
<tbody>
{items.map((item, idx) => {
const evenodd = idx % 2 ? 'even' : 'odd';
return (
<tr key={item.item_id}>
<td className={evenodd}>
<ItemType.List lang={lang} item={item} />
</td>
</tr>
);
})}
</tbody>
</table>
);
};
componentDidMount() {
this.updateItems();
}
componentDidUpdate(prevProps: Props, prevState: State) {
const prevItemIds = prevProps.item.item_link;
const nextItemIds = this.props.item.item_link;
if (prevItemIds.toString() !== nextItemIds.toString()) {
this.updateItems();
}
}
updateItems() {
const { item } = this.props;
const itemIds = item.item_link;
ItemUtil.getList(itemIds, (results) => {
const items = results.data;
this.setState({ items });
});
}
render() {
const { lang } = this.props;
return (
<table className="listTable">
<tbody>
{this.state.items.map((item, idx) => {
const evenodd = idx % 2 ? "even" : "odd";
return (
<tr key={item.item_id}>
<td className={evenodd}>
<ItemType.List lang={lang} item={item} />
</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
class BinderDetail extends DetailBase {
getFields() {
const { lang } = this.props;
const item = this.props.item as ItemBinder;
return [
{ label: "ID", value: item.doi },
{ label: "[en]Title[/en][ja]タイトル[/ja]", value: Functions.mlang(item.title, lang) },
{ label: "[en]Free Keywords[/en][ja]フリーキーワード[/ja]", value: <ItemTypeField.FreeKeyword lang={lang} keyword={item.keyword} /> },
{ label: "[en]Description[/en][ja]概要[/ja]", value: <ItemTypeField.Description lang={lang} description={item.description} /> },
{ label: "[en]Last Modified Date[/en][ja]最終更新日[/ja]", value: <ItemTypeField.DateTime lang={lang} date={item.last_update_date} /> },
{ label: "[en]Created Date[/en][ja]作成日[/ja]", value: <ItemTypeField.DateTime lang={lang} date={item.creation_date} /> },
{ label: "[en]Contributor[/en][ja]登録者[/ja]", value: <ItemTypeField.Contributer lang={lang} uname={item.uname} name={item.name} /> },
{ label: "[en]Item Type[/en][ja]アイテムタイプ[/ja]", value: item.item_type_display_name },
{ label: "[en]Change Log(History)[/en][ja]変更履歴[/ja]", value: <ItemTypeField.ChangeLog lang={lang} changelog={item.changelog} /> },
{ label: "Index", value: <ItemTypeField.ItemIndex lang={lang} index={item.index} /> },
];
}
render() {
const { lang } = this.props;
const item = this.props.item as ItemBinder;
const detail = super.render.call(this);
return (
<>
{detail}
<h4>Registered Items</h4>
<BinderLinkItems lang={lang} item={item} />
</>
);
}
}
const BinderDetail: React.FC<DetailBaseProps> = (props) => {
const { lang, item } = props;
const binderItem = item as ItemBinder;
return (
<>
<DetailBase
{...props}
getFields={({ lang }) => {
return [
{ label: 'ID', value: binderItem.doi },
{ label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(binderItem.title, lang) },
{
label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]',
value: <ItemTypeField.FreeKeyword lang={lang} keyword={binderItem.keyword} />,
},
{
label: '[en]Description[/en][ja]概要[/ja]',
value: <ItemTypeField.Description lang={lang} description={binderItem.description} />,
},
{
label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]',
value: <ItemTypeField.DateTime lang={lang} date={binderItem.last_update_date} />,
},
{
label: '[en]Created Date[/en][ja]作成日[/ja]',
value: <ItemTypeField.DateTime lang={lang} date={binderItem.creation_date} />,
},
{
label: '[en]Contributor[/en][ja]登録者[/ja]',
value: <ItemTypeField.Contributer lang={lang} uname={binderItem.uname} name={binderItem.name} />,
},
{ label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: binderItem.item_type_display_name },
{
label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]',
value: <ItemTypeField.ChangeLog lang={lang} changelog={binderItem.changelog} />,
},
{ label: 'Index', value: <ItemTypeField.ItemIndex lang={lang} index={binderItem.index} /> },
];
}}
/>
<h4>Registered Items</h4>
<BinderLinkItems lang={lang} item={binderItem} />
</>
);
};
export default BinderDetail;
+23 -24
View File
@@ -1,27 +1,26 @@
import { Link } from "react-router-dom";
import Functions from "../../../functions";
import iconFile from "../../assets/images/icon_binder.gif";
import { ItemBinder } from "../../lib/ItemUtil";
import ListBase, { ListBaseProps } from "../lib/ListBase";
import type React from 'react';
import { Link } from 'react-router-dom';
import Functions from '../../../functions';
import iconFile from '../../assets/images/icon_binder.gif';
import type { ItemBinder } from '../../lib/ItemUtil';
import ListBase, { type ListBaseProps } from '../lib/ListBase';
class BinderList extends ListBase {
constructor(props: ListBaseProps) {
super(props);
this.label = "Binder";
this.icon = iconFile;
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemBinder;
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
{Functions.mlang(item.description, lang)}
</>
);
}
}
const BinderList: React.FC<ListBaseProps> = (props) => (
<ListBase
{...props}
label="Binder"
icon={iconFile}
renderBody={({ lang, item, url }) => {
const binderItem = item as ItemBinder;
return (
<>
<Link to={url}>{Functions.mlang(binderItem.title, lang)}</Link>
<br />
{Functions.mlang(binderItem.description, lang)}
</>
);
}}
/>
);
export default BinderList;
+11 -11
View File
@@ -1,14 +1,14 @@
import iconFile from "../../assets/images/icon_binder.gif";
import TopBase, { TopBaseProps } from "../lib/TopBase";
import iconFile from '../../assets/images/icon_binder.gif';
import TopBase, { type TopBaseProps } from '../lib/TopBase';
class BinderTop extends TopBase {
constructor(props: TopBaseProps) {
super(props);
this.type = "binder";
this.label = "Binder";
this.icon = iconFile;
this.description = "[en]Binder collection.[/en][ja]バインダー[/ja]";
}
}
const BinderTop: React.FC<TopBaseProps> = (props) => (
<TopBase
{...props}
type="binder"
label="Binder"
icon={iconFile}
description="[en]Binder collection.[/en][ja]バインダー[/ja]"
/>
);
export default BinderTop;
+8 -8
View File
@@ -1,13 +1,13 @@
import BinderAdvancedSearch from "./BinderAdvancedSearch";
import BinderDetail from "./BinderDetail";
import BinderList from "./BinderList";
import BinderTop from "./BinderTop";
import BinderAdvancedSearch from './BinderAdvancedSearch';
import BinderDetail from './BinderDetail';
import BinderList from './BinderList';
import BinderTop from './BinderTop';
const ItemTypeBinder = {
Top: BinderTop,
List: BinderList,
Detail: BinderDetail,
AdvancedSearch: BinderAdvancedSearch,
Top: BinderTop,
List: BinderList,
Detail: BinderDetail,
AdvancedSearch: BinderAdvancedSearch,
};
export default ItemTypeBinder;
@@ -1,32 +1,53 @@
import AdvancedSearchBase, { AdvancedSearchBaseProps } from "../lib/AdvancedSearchBase";
import { useCallback } from 'react';
import AdvancedSearchBase, { type AdvancedSearchBaseProps, type GetRowsContext } from '../lib/AdvancedSearchBase';
class BookAdvancedSearch extends AdvancedSearchBase {
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = "book";
this.title = "Book";
const keys = ["title", "keyword", "description", "doi", "author", "editor", "publisher", "publication_year", "isbn", "file.book_pdf.original_file_name"];
const show = props.query.hasType(this.type);
const values: any = {};
keys.forEach((key) => (values[key] = props.query.get(this.type, key)));
this.state = { show, values };
}
const BookAdvancedSearch = (props: AdvancedSearchBaseProps) => {
const { query } = props;
const type = 'book';
const keys = [
'title',
'keyword',
'description',
'doi',
'author',
'editor',
'publisher',
'publication_year',
'isbn',
'file.book_pdf.original_file_name',
];
const initialValues: Record<string, string> = {};
keys.forEach((key) => (initialValues[key] = query.get(type, key)));
const initialShow = query.hasType(type);
getRows() {
const rows = [
{ label: "[en]Book Title[/en][ja]著書名[/ja]", value: this.renderFieldInputText("title", 50) },
{ label: "[en]Free Keywords[/en][ja]フリーキーワード[/ja]", value: this.renderFieldInputText("keyword", 50) },
{ label: "[en]Description[/en][ja]概要[/ja]", value: this.renderFieldInputText("description", 50) },
{ label: "ID", value: this.renderFieldInputText("doi", 50) },
{ label: "[en]Author[/en][ja]著者[/ja]", value: this.renderFieldInputText("author", 50) },
{ label: "[en]Editor[/en][ja]編集者[/ja]", value: this.renderFieldInputText("editor", 50) },
{ label: "[en]Publisher[/en][ja]出版社[/ja]", value: this.renderFieldInputText("publisher", 50) },
{ label: "[en]Publication Year[/en][ja]出版年[/ja]", value: this.renderFieldInputText("publication_year", 10) },
{ label: "ISBN", value: this.renderFieldInputText("isbn", 50) },
{ label: "[en]PDF File[/en][ja]PDF ファイル[/ja]", value: this.renderFieldInputText("file.book_pdf.original_file_name", 50) },
];
return rows;
}
}
const getRows = useCallback((ctx: GetRowsContext) => {
return [
{ label: '[en]Book Title[/en][ja]著書名[/ja]', value: ctx.renderFieldInputText('title', 50) },
{ label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]', value: ctx.renderFieldInputText('keyword', 50) },
{ label: '[en]Description[/en][ja]概要[/ja]', value: ctx.renderFieldInputText('description', 50) },
{ label: 'ID', value: ctx.renderFieldInputText('doi', 50) },
{ label: '[en]Author[/en][ja]著者[/ja]', value: ctx.renderFieldInputText('author', 50) },
{ label: '[en]Editor[/en][ja]編集者[/ja]', value: ctx.renderFieldInputText('editor', 50) },
{ label: '[en]Publisher[/en][ja]出版社[/ja]', value: ctx.renderFieldInputText('publisher', 50) },
{ label: '[en]Publication Year[/en][ja]出版年[/ja]', value: ctx.renderFieldInputText('publication_year', 10) },
{ label: 'ISBN', value: ctx.renderFieldInputText('isbn', 50) },
{
label: '[en]PDF File[/en][ja]PDF ファイル[/ja]',
value: ctx.renderFieldInputText('file.book_pdf.original_file_name', 50),
},
];
}, []);
return (
<AdvancedSearchBase
{...props}
type={type}
title="Book"
initialValues={initialValues}
initialShow={initialShow}
getRows={getRows}
/>
);
};
export default BookAdvancedSearch;
+73 -37
View File
@@ -1,40 +1,76 @@
import Functions from "../../../functions";
import { ItemBook } from "../../lib/ItemUtil";
import DetailBase from "../lib/DetailBase";
import ItemTypeField from "../lib/field";
import Functions from '../../../functions';
import type { ItemBook } from '../../lib/ItemUtil';
import DetailBase, { type DetailBaseProps } from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
class BookDetail extends DetailBase {
getFields() {
const { lang } = this.props;
const item = this.props.item as ItemBook;
return [
{ label: "ID", value: item.doi },
{ label: "[en]Language[/en][ja]言語[/ja]", value: <ItemTypeField.Language lang={lang} itemLang={item.lang} /> },
{ label: "[en]Book Title[/en][ja]著書名[/ja]", value: Functions.mlang(item.title, lang) },
{ label: "[en]Free Keywords[/en][ja]フリーキーワード[/ja]", value: <ItemTypeField.FreeKeyword lang={lang} keyword={item.keyword} /> },
{ label: "[en]Description[/en][ja]概要[/ja]", value: <ItemTypeField.Description lang={lang} description={item.description} /> },
{ label: "[en]Last Modified Date[/en][ja]最終更新日[/ja]", value: <ItemTypeField.DateTime lang={lang} date={item.last_update_date} /> },
{ label: "[en]Created Date[/en][ja]作成日[/ja]", value: <ItemTypeField.DateTime lang={lang} date={item.creation_date} /> },
{ label: "[en]Contributor[/en][ja]登録者[/ja]", value: <ItemTypeField.Contributer lang={lang} uname={item.uname} name={item.name} /> },
{ label: "[en]Item Type[/en][ja]アイテムタイプ[/ja]", value: item.item_type_display_name },
{ label: "[en]Change Log(History)[/en][ja]変更履歴[/ja]", value: <ItemTypeField.ChangeLog lang={lang} changelog={item.changelog} /> },
{ label: "[en]Author[/en][ja]著者[/ja]", value: <ItemTypeField.Author lang={lang} author={item.author} /> },
{ label: "[en]Editor[/en][ja]編集者[/ja]", value: Functions.mlang(item.editor, lang) },
{ label: "[en]Publisher[/en][ja]出版社[/ja]", value: Functions.mlang(item.publisher, lang) },
{ label: "[en]Publication Year[/en][ja]出版年[/ja]", value: item.publication_year },
{
label: "URL",
value: (
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.url}
</a>
),
},
{ label: "[en]PDF File[/en][ja]PDF ファイル[/ja]", value: <ItemTypeField.ItemFile lang={lang} file={item.file} type="book_pdf" downloadLimit={item.attachment_dl_limit} /> },
{ label: "Index", value: <ItemTypeField.ItemIndex lang={lang} index={item.index} /> },
{ label: "[en]Related to[/en][ja]関連アイテム[/ja]", value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} /> },
];
}
}
const BookDetail: React.FC<DetailBaseProps> = (props) => (
<DetailBase
{...props}
getFields={({ lang, item }) => {
const bookItem = item as ItemBook;
return [
{ label: 'ID', value: bookItem.doi },
{
label: '[en]Language[/en][ja]言語[/ja]',
value: <ItemTypeField.Language lang={lang} itemLang={bookItem.lang} />,
},
{ label: '[en]Book Title[/en][ja]著書名[/ja]', value: Functions.mlang(bookItem.title, lang) },
{
label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]',
value: <ItemTypeField.FreeKeyword lang={lang} keyword={bookItem.keyword} />,
},
{
label: '[en]Description[/en][ja]概要[/ja]',
value: <ItemTypeField.Description lang={lang} description={bookItem.description} />,
},
{
label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]',
value: <ItemTypeField.DateTime lang={lang} date={bookItem.last_update_date} />,
},
{
label: '[en]Created Date[/en][ja]作成日[/ja]',
value: <ItemTypeField.DateTime lang={lang} date={bookItem.creation_date} />,
},
{
label: '[en]Contributor[/en][ja]登録者[/ja]',
value: <ItemTypeField.Contributer lang={lang} uname={bookItem.uname} name={bookItem.name} />,
},
{ label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: bookItem.item_type_display_name },
{
label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]',
value: <ItemTypeField.ChangeLog lang={lang} changelog={bookItem.changelog} />,
},
{ label: '[en]Author[/en][ja]著者[/ja]', value: <ItemTypeField.Author lang={lang} author={bookItem.author} /> },
{ label: '[en]Editor[/en][ja]編集者[/ja]', value: Functions.mlang(bookItem.editor, lang) },
{ label: '[en]Publisher[/en][ja]出版社[/ja]', value: Functions.mlang(bookItem.publisher, lang) },
{ label: '[en]Publication Year[/en][ja]出版年[/ja]', value: bookItem.publication_year },
{
label: 'URL',
value: (
<a href={bookItem.url} target="_blank" rel="noopener noreferrer">
{bookItem.url}
</a>
),
},
{
label: '[en]PDF File[/en][ja]PDF ファイル[/ja]',
value: (
<ItemTypeField.ItemFile
lang={lang}
file={bookItem.file}
type="book_pdf"
downloadLimit={bookItem.attachment_dl_limit}
/>
),
},
{ label: 'Index', value: <ItemTypeField.ItemIndex lang={lang} index={bookItem.index} /> },
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={bookItem.related_to} />,
},
];
}}
/>
);
export default BookDetail;
+30 -33
View File
@@ -1,36 +1,33 @@
import { Fragment } from "react";
import { Link } from "react-router-dom";
import Functions from "../../../functions";
import iconFile from "../../assets/images/icon_book.gif";
import { ItemBook } from "../../lib/ItemUtil";
import ListBase, { ListBaseProps } from "../lib/ListBase";
import type React from 'react';
import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import Functions from '../../../functions';
import iconFile from '../../assets/images/icon_book.gif';
import type { ItemBook } from '../../lib/ItemUtil';
import ListBase, { type ListBaseProps } from '../lib/ListBase';
class BookList extends ListBase {
constructor(props: ListBaseProps) {
super(props);
this.label = "Book";
this.icon = iconFile;
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemBook;
const authors = item.author.map((author, i) => {
return (
<Fragment key={i}>
{i > 0 && ", "}
{Functions.mlang(author, lang)}
</Fragment>
);
});
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
{authors}
</>
);
}
}
const BookList: React.FC<ListBaseProps> = (props) => (
<ListBase
{...props}
label="Book"
icon={iconFile}
renderBody={({ lang, item, url }) => {
const bookItem = item as ItemBook;
const authors = bookItem.author.map((author, i) => (
<Fragment key={i}>
{i > 0 && ', '}
{Functions.mlang(author, lang)}
</Fragment>
));
return (
<>
<Link to={url}>{Functions.mlang(bookItem.title, lang)}</Link>
<br />
{authors}
</>
);
}}
/>
);
export default BookList;
+11 -11
View File
@@ -1,14 +1,14 @@
import iconFile from "../../assets/images/icon_book.gif";
import TopBase, { TopBaseProps } from "../lib/TopBase";
import iconFile from '../../assets/images/icon_book.gif';
import TopBase, { type TopBaseProps } from '../lib/TopBase';
class BookTop extends TopBase {
constructor(props: TopBaseProps) {
super(props);
this.type = "book";
this.label = "Book";
this.icon = iconFile;
this.description = "[en]Related book collection.[/en][ja]関連書籍[/ja]";
}
}
const BookTop: React.FC<TopBaseProps> = (props) => (
<TopBase
{...props}
type="book"
label="Book"
icon={iconFile}
description="[en]Related book collection.[/en][ja]関連書籍[/ja]"
/>
);
export default BookTop;
+8 -8
View File
@@ -1,13 +1,13 @@
import BookAdvancedSearch from "./BookAdvancedSearch";
import BookDetail from "./BookDetail";
import BookList from "./BookList";
import BookTop from "./BookTop";
import BookAdvancedSearch from './BookAdvancedSearch';
import BookDetail from './BookDetail';
import BookList from './BookList';
import BookTop from './BookTop';
const ItemTypeBook = {
Top: BookTop,
List: BookList,
Detail: BookDetail,
AdvancedSearch: BookAdvancedSearch,
Top: BookTop,
List: BookList,
Detail: BookDetail,
AdvancedSearch: BookAdvancedSearch,
};
export default ItemTypeBook;
@@ -1,50 +1,63 @@
import { ItemConferenceSubTypes } from "../../lib/ItemUtil";
import AdvancedSearchBase, { AdvancedSearchBaseProps } from "../lib/AdvancedSearchBase";
import { useCallback } from 'react';
import { ItemConferenceSubTypes } from '../../lib/ItemUtil';
import AdvancedSearchBase, { type AdvancedSearchBaseProps, type GetRowsContext } from '../lib/AdvancedSearchBase';
class ConferenceAdvancedSearch extends AdvancedSearchBase {
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = "conference";
this.title = "Conference";
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1);
const mday = String(now.getDate());
this.state.values["title"] = "";
this.state.values["presentation_type"] = "";
this.state.values["author"] = "";
this.state.values["conference_from_year"] = year;
this.state.values["conference_from_month"] = month;
this.state.values["conference_from_mday"] = mday;
this.state.values["conference_to_year"] = year;
this.state.values["conference_to_month"] = month;
this.state.values["conference_to_mday"] = mday;
this.setIgnoreKey("conference_from_year");
this.setIgnoreKey("conference_from_month");
this.setIgnoreKey("conference_from_mday");
this.setIgnoreKey("conference_to_year");
this.setIgnoreKey("conference_to_month");
this.setIgnoreKey("conference_to_mday");
}
const ConferenceAdvancedSearch = (props: AdvancedSearchBaseProps) => {
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1);
const mday = String(now.getDate());
const initialValues: Record<string, string> = {
title: '',
presentation_type: '',
author: '',
conference_from_year: year,
conference_from_month: month,
conference_from_mday: mday,
conference_to_year: year,
conference_to_month: month,
conference_to_mday: mday,
};
const ignoreKeys = [
'conference_from_year',
'conference_from_month',
'conference_from_mday',
'conference_to_year',
'conference_to_month',
'conference_to_mday',
];
renderDate() {
return (
<>
<div>{this.renderFieldDate("From", "conference_from_year", "conference_from_month", "conference_from_mday")}</div>
<div>{this.renderFieldDate("To", "conference_to_year", "conference_to_month", "conference_to_mday")}</div>
</>
);
}
const getRows = useCallback((ctx: GetRowsContext) => {
const renderDate = () => (
<>
<div>
{ctx.renderFieldDate('From', 'conference_from_year', 'conference_from_month', 'conference_from_mday')}
</div>
<div>{ctx.renderFieldDate('To', 'conference_to_year', 'conference_to_month', 'conference_to_mday')}</div>
</>
);
return [
{ label: '[en]Presentation Title[/en][ja]発表議題[/ja]', value: ctx.renderFieldInputText('title', 50) },
{
label: '[en]Presentation Type[/en][ja]発表資料ファイル形式[/ja]',
value: ctx.renderFieldSelect('presentation_type', ItemConferenceSubTypes),
},
{ label: '[en]Author[/en][ja]発表者[/ja]', value: ctx.renderFieldInputText('author', 50) },
{ label: '[en]Date[/en][ja]日付[/ja]', value: renderDate() },
];
}, []);
getRows() {
const rows = [
{ label: "[en]Presentation Title[/en][ja]発表議題[/ja]", value: this.renderFieldInputText("title", 50) },
{ label: "[en]Presentation Type[/en][ja]発表資料ファイル形式[/ja]", value: this.renderFieldSelect("presentation_type", ItemConferenceSubTypes) },
{ label: "[en]Author[/en][ja]発表者[/ja]", value: this.renderFieldInputText("author", 50) },
{ label: "[en]Date[/en][ja]日付[/ja]", value: this.renderDate() },
];
return rows;
}
}
return (
<AdvancedSearchBase
{...props}
type="conference"
title="Conference"
initialValues={initialValues}
initialShow={false}
ignoreKeys={ignoreKeys}
getRows={getRows}
/>
);
};
export default ConferenceAdvancedSearch;
@@ -1,34 +1,75 @@
import Functions from "../../../functions";
import { ItemConference } from "../../lib/ItemUtil";
import DetailBase from "../lib/DetailBase";
import ItemTypeField from "../lib/field";
import ConferenceUtil from "./ConferenceUtil";
import Functions from '../../../functions';
import type { ItemConference } from '../../lib/ItemUtil';
import DetailBase, { type DetailBaseProps } from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
import ConferenceUtil from './ConferenceUtil';
class ConferenceDetail extends DetailBase {
getFields() {
const { lang } = this.props;
const item = this.props.item as ItemConference;
return [
{ label: "ID", value: item.doi },
{ label: "[en]Language[/en][ja]言語[/ja]", value: <ItemTypeField.Language lang={lang} itemLang={item.lang} /> },
{ label: "[en]Conference Title[/en][ja]学会名[/ja]", value: Functions.mlang(item.conference_title, lang) },
{ label: "[en]Place[/en][ja]開催地[/ja]", value: item.place },
{ label: "[en]Date[/en][ja]日付[/ja]", value: <ConferenceUtil.ConferenceDate lang={lang} item={item} /> },
{ label: "[en]Last Modified Date[/en][ja]最終更新日[/ja]", value: <ItemTypeField.DateTime lang={lang} date={item.last_update_date} /> },
{ label: "[en]Created Date[/en][ja]作成日[/ja]", value: <ItemTypeField.DateTime lang={lang} date={item.creation_date} /> },
{ label: "[en]Contributor[/en][ja]登録者[/ja]", value: <ItemTypeField.Contributer lang={lang} uname={item.uname} name={item.name} /> },
{ label: "[en]Item Type[/en][ja]アイテムタイプ[/ja]", value: item.item_type_display_name },
{ label: "[en]Change Log(History)[/en][ja]変更履歴[/ja]", value: <ItemTypeField.ChangeLog lang={lang} changelog={item.changelog} /> },
{ label: "[en]Presentation Title[/en][ja]発表議題[/ja]", value: Functions.mlang(item.title, lang) },
{ label: "[en]Author[/en][ja]発表者[/ja]", value: <ItemTypeField.Author lang={lang} author={item.author} /> },
{ label: "[en]Abstract[/en][ja]要約[/ja]", value: <ItemTypeField.Description lang={lang} description={item.abstract} /> },
{ label: "[en]Presentation File[/en][ja]発表資料[/ja]", value: <ItemTypeField.ItemFile lang={lang} file={item.file} type="conference_file" /> },
{ label: "[en]Presentation Type[/en][ja]発表資料ファイル形式[/ja]", value: <ConferenceUtil.PresentationType lang={lang} type={item.presentation_type} /> },
{ label: "[en]Conference Paper[/en][ja]学会資料[/ja]", value: <ItemTypeField.ItemFile lang={lang} file={item.file} type="conference_paper" /> },
{ label: "Index", value: <ItemTypeField.ItemIndex lang={lang} index={item.index} /> },
{ label: "[en]Related to[/en][ja]関連アイテム[/ja]", value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} /> },
];
}
}
const ConferenceDetail: React.FC<DetailBaseProps> = (props) => (
<DetailBase
{...props}
getFields={({ lang, item }) => {
const conferenceItem = item as ItemConference;
return [
{ label: 'ID', value: conferenceItem.doi },
{
label: '[en]Language[/en][ja]言語[/ja]',
value: <ItemTypeField.Language lang={lang} itemLang={conferenceItem.lang} />,
},
{
label: '[en]Conference Title[/en][ja]学会名[/ja]',
value: Functions.mlang(conferenceItem.conference_title, lang),
},
{ label: '[en]Place[/en][ja]開催地[/ja]', value: conferenceItem.place },
{
label: '[en]Date[/en][ja]日付[/ja]',
value: <ConferenceUtil.ConferenceDate lang={lang} item={conferenceItem} />,
},
{
label: '[en]Last Modified Date[/en][ja]最終更新日[/ja]',
value: <ItemTypeField.DateTime lang={lang} date={conferenceItem.last_update_date} />,
},
{
label: '[en]Created Date[/en][ja]作成日[/ja]',
value: <ItemTypeField.DateTime lang={lang} date={conferenceItem.creation_date} />,
},
{
label: '[en]Contributor[/en][ja]登録者[/ja]',
value: <ItemTypeField.Contributer lang={lang} uname={conferenceItem.uname} name={conferenceItem.name} />,
},
{ label: '[en]Item Type[/en][ja]アイテムタイプ[/ja]', value: conferenceItem.item_type_display_name },
{
label: '[en]Change Log(History)[/en][ja]変更履歴[/ja]',
value: <ItemTypeField.ChangeLog lang={lang} changelog={conferenceItem.changelog} />,
},
{ label: '[en]Presentation Title[/en][ja]発表議題[/ja]', value: Functions.mlang(conferenceItem.title, lang) },
{
label: '[en]Author[/en][ja]発表者[/ja]',
value: <ItemTypeField.Author lang={lang} author={conferenceItem.author} />,
},
{
label: '[en]Abstract[/en][ja]要約[/ja]',
value: <ItemTypeField.Description lang={lang} description={conferenceItem.abstract} />,
},
{
label: '[en]Presentation File[/en][ja]発表資料[/ja]',
value: <ItemTypeField.ItemFile lang={lang} file={conferenceItem.file} type="conference_file" />,
},
{
label: '[en]Presentation Type[/en][ja]発表資料ファイル形式[/ja]',
value: <ConferenceUtil.PresentationType lang={lang} type={conferenceItem.presentation_type} />,
},
{
label: '[en]Conference Paper[/en][ja]学会資料[/ja]',
value: <ItemTypeField.ItemFile lang={lang} file={conferenceItem.file} type="conference_paper" />,
},
{ label: 'Index', value: <ItemTypeField.ItemIndex lang={lang} index={conferenceItem.index} /> },
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={conferenceItem.related_to} />,
},
];
}}
/>
);
export default ConferenceDetail;
@@ -1,38 +1,36 @@
import { Fragment } from "react";
import { Link } from "react-router-dom";
import Functions from "../../../functions";
import iconFile from "../../assets/images/icon_conference.gif";
import { ItemConference } from "../../lib/ItemUtil";
import ListBase, { ListBaseProps } from "../lib/ListBase";
import ConferenceUtil from "./ConferenceUtil";
import type React from 'react';
import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import Functions from '../../../functions';
import iconFile from '../../assets/images/icon_conference.gif';
import type { ItemConference } from '../../lib/ItemUtil';
import ListBase, { type ListBaseProps } from '../lib/ListBase';
import ConferenceUtil from './ConferenceUtil';
class ConferenceList extends ListBase {
constructor(props: ListBaseProps) {
super(props);
this.label = "Conference";
this.icon = iconFile;
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemConference;
const authors = item.author.map((author, i) => {
return (
<Fragment key={i}>
{i > 0 && ", "}
{Functions.mlang(author, lang)}
</Fragment>
);
});
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
{Functions.mlang(item.conference_title, lang)} (<ConferenceUtil.PresentationType lang={lang} type={item.presentation_type} />)<br />
{authors}
</>
);
}
}
const ConferenceList: React.FC<ListBaseProps> = (props) => (
<ListBase
{...props}
label="Conference"
icon={iconFile}
renderBody={({ lang, item, url }) => {
const confItem = item as ItemConference;
const authors = confItem.author.map((author, i) => (
<Fragment key={i}>
{i > 0 && ', '}
{Functions.mlang(author, lang)}
</Fragment>
));
return (
<>
<Link to={url}>{Functions.mlang(confItem.title, lang)}</Link>
<br />
{Functions.mlang(confItem.conference_title, lang)} (
<ConferenceUtil.PresentationType lang={lang} type={confItem.presentation_type} />)<br />
{authors}
</>
);
}}
/>
);
export default ConferenceList;

Some files were not shown because too many files have changed in this diff Show More