10 Commits
Author SHA1 Message Date
orrisroot 15c94f9c35 fix: keep text dark on form controls given a white background
sanitize.css forms.css makes form controls inherit the text color, so
the rules that put them back on white left white text wherever the
surrounding panel sets it, as in the nimgsearch search panel and the
open.neuroinf.jp advanced search.

- set the text to black next to each white control background,
  matching how the browser drew the controls before sanitize.css
2026-09-15 20:02:08 +09:00
orrisroot d1d947c313 fix: draw the index tree lines at the right depth
The index tree has a single root, so its first indent unit is always
marked as an end and every line was shifted one column to the right,
doubling the vertical line under the root. Hide that unit and draw the
node's own branch (├ or └) after the indent from the leaf-last class,
as nimg.neuroinf.jp already does.
2026-09-15 18:27:17 +09:00
orrisroot 5fb467a76c fix: serve data files with reserved characters in dev and preview
sirv decodes request paths with decodeURI, which leaves %26 (&) and
%23 (#) encoded, so data files with such characters in their names
were not found by the dev and preview servers. Hand sirv the fully
decoded path, the way Apache resolves it in production.
2026-09-14 18:33:36 +09:00
orrisroot 6e6e9ea9d9 fix: put form controls back on a white background
sanitize.css forms.css makes inputs, selects and buttons transparent.
Give them a white background through the classes and blocks they are
used in.
2026-09-14 16:02:59 +09:00
orrisroot 81d075579f docs: note that every converted site uses sanitize.css 2026-09-14 15:28:54 +09:00
orrisroot 6098ce2445 feat: enable GA4 tracking
Set the GA4 measurement ID G-C186H597TK and report page views the same
way as the other converted sites.

- initialize react-ga4 once at module load instead of on every render
- disable gtag's automatic page_view and send one per route change
- only track production builds
- describe the tracking setup in the README
2026-09-14 13:46:54 +09:00
orrisroot dead6ab2ac docs: describe the data layout and deployment 2026-09-14 13:19:51 +09:00
orrisroot 20dd4f2a72 feat: serve the atlas data from XOOPS-style paths
v2.0.0 put every atlas under /data/<atlas>/, which broke the original
/<atlas>/static/ download links and kept the data out of the modules/
layout the other sites use.

- read tree.json and items.json from /<atlas>/modules/xoonips/ and
  link the files under /<atlas>/modules/xoonips/file/
- redirect download.php, the v2.0.0 /data/ paths and the older
  /<atlas>/file/ paths with a 301
- send directories without an index.html to the SPA as well: the
  atlas directories now exist on disk, and the old rule served them
  as a 404
2026-09-14 13:19:50 +09:00
orrisroot 00f375cd9e feat: migrate to Vite 8, React 19 and Biome
The Vite 4 conversion predates the stack the other converted sites
settled on. Bring it in line without changing any URL.

- Vite 4 with SWC -> Vite 8, React 18 -> 19, TypeScript 5 -> 7,
  react-router-dom v6 -> react-router v8
- swap axios -> ky, moment -> date-fns and react-helmet-async ->
  @dr.pogodin/react-helmet, and replace react-router-hash-link and
  react-spinner-material with local code
- drop xregexp, react-app-polyfill and the node polyfill and
  rewrite-all plugins
- mount ../static-contents with sirv in the dev and preview servers,
  and split vendor-react from vendor-misc
- keep sanitize.css: the theme was written on top of it, and swapping
  it for modern-normalize moves nearly every element
- replace ESLint and Prettier with Biome and fix what it reports:
  stable list keys, button types, the shadowed escape/unescape
  helpers and the unused LinkImage component
2026-09-14 13:19:29 +09:00
orrisroot a2564c5466 chore: drop the eslint, prettier and vscode setup
Biome takes over linting and formatting, and the VS Code settings
only wired up Prettier.
2026-09-14 13:19:01 +09:00
168 changed files with 8588 additions and 12291 deletions
-29
View File
@@ -1,29 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
'plugin:react-hooks/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
settings: {
react: {
version: 'detect',
},
},
};
+1 -6
View File
@@ -10,14 +10,12 @@ lerna-debug.log*
node_modules
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/launch.json
!.vscode/settings.json
!.vscode/tasks.json
.idea
.DS_Store
*.suo
@@ -25,6 +23,3 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# customize
/public/data
+6
View File
@@ -0,0 +1,6 @@
// Keep @types/node on the 24.x line to match the deployment target. Its
// "latest" dist-tag points at the 22.x line, so the default target would
// report no updates at all instead of offering 24.x patches.
export default {
target: (name) => (name === '@types/node' ? 'minor' : 'latest'),
};
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": true,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "all"
}
-7
View File
@@ -1,7 +0,0 @@
{
"recommendations": [
"jawandarajbir.react-vscode-extension-pack",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker"
]
}
-12
View File
@@ -1,12 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Vite Debugger",
"type": "chrome",
"request": "launch",
"url": "http://localhost:5173/",
"webRoot": "${workspaceFolder}"
}
]
}
-71
View File
@@ -1,71 +0,0 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Extensions - Code Spell Checker
"cSpell.ignoreWords": ["brainatlas", "degu", "Neuroinformatics", "octodon", "RIKEN"],
"cSpell.words": [
"dobr",
"doesn",
"dohtml",
"doimage",
"dosmiley",
"doxcode",
"htmlspecialchars",
"itemcount",
"itemselect",
"itemsubtypesearch",
"itemtype",
"itemtypes",
"itemtypesearch",
"justsystem",
"lightbox",
"listitem",
"lokijs",
"mathematica",
"mday",
"mlang",
"navi",
"orderdir",
"pageview",
"pubmed",
"quicksearch",
"simpf",
"simplesort",
"subitemtype",
"xoonips",
"xoops"
],
// Extensions - HTML
"html.format.wrapLineLength": 0,
// Extentions - Prettier
// - see: .prettierrc.json
// Extentions - Typescript
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.updateImportsOnFileMove.enabled": "always"
}
-40
View File
@@ -1,40 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "npm: dev",
"detail": "vite dev",
"type": "npm",
"script": "dev",
"problemMatcher": "$tsc",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "npm: build",
"detail": "vite build",
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": "$tsc",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
}
]
}
+106 -2
View File
@@ -1,3 +1,107 @@
# Brain Atlas (by Laboratory for Symbolic Cognitive Development, BSI)
# BSI-NI Brain Atlas
React project for brainatlas.brain.riken.jp
Static front-end for the archived [BSI-NI Brain Atlas](https://brainatlas.brain.riken.jp/)
site (Laboratory for Symbolic Cognitive Development, RIKEN BSI), built with
[Vite](https://vite.dev/), React and TypeScript.
One app serves three former XOOPS installations side by side: `degu`, `jm`
(Japanese Macaque Monkey) and `marmoset`.
## Requirements
- Node.js 20.19+ / 22.12+ (Vite 8)
- npm
## Setup
```sh
npm install
```
## Site data
The pages are rendered from JSON dumped out of the original XooNIps
installations. All of it lives in `static-contents/`, which sits next to the
repository rather than inside it, laid out the way the XOOPS sites served it:
```
brainatlas.brain.riken.jp/
├── brainatlas.brain.riken.jp/ this repository
└── static-contents/
├── degu/
│ ├── modules/xoonips/ tree.json, items.json, file/<id>/
│ └── static/ the 3D atlas databases (*.sqlite) and manuals
├── jm/ same shape
└── marmoset/ same shape
```
Two things inside it carry absolute paths and have to move with the layout:
the `url` fields in each `items.json`, and the `RewriteBase` in every
`file/<id>/.htaccess`, which redirects a bare file directory to the file.
The dev and preview servers mount that directory at the site root. It is
deliberately kept out of `dist/`: it runs to about 50 GB, and the web server
maps it in directly. It is not tracked in git.
## Scripts
| Command | Description |
| --- | --- |
| `npm run dev` | Start the dev server on http://localhost:5173 |
| `npm run build` | Type-check and build into `dist/` |
| `npm run preview` | Serve the production build on http://localhost:4173 |
| `npm run lint` | Lint with [Biome](https://biomejs.dev/) |
| `npm run format` | Format with Biome |
| `npm run check` | Lint, format and organize imports, applying the safe fixes |
`@types/node` is pinned to the major that matches the Node runtime in use (24);
`.ncurc.js` keeps `npm-check-updates` from bumping it past that.
The base stylesheet is `sanitize.css` (with its `forms` and `typography`
sheets), the same as the other converted sites.
There is currently no test suite.
## Deployment
Build locally and copy the result up; the server does not need a toolchain.
```sh
npm ci
npm run check
npm run build
../deploy.sh
```
`deploy.sh`, next to `static-contents/`, sends the data to
`archive:/data/www/brainatlas.brain.riken.jp/static-contents/`, then `dist/` to
`.../html/`, and finally links every top-level entry of `static-contents/` into
the document root over ssh (`ln -sfn ../static-contents/* .`). The data has to
go first: the document root's `rsync --delete` removes those links, and they
are only put back at the end.
Apache needs `Options FollowSymLinks` and `AllowOverride All` on both
directories. Use symlinks rather than `Alias`: an `Alias` maps the URL outside
the document root, so `.htaccess` no longer applies and the SPA fallback and
the legacy redirects stop working.
The atlas directories are real directories on disk as well as SPA routes
(`/degu/`, `/degu/modules/xoonips/listitem.php`, ...). `public/.htaccess`
therefore sends every request that is not a real file to `index.html`,
directories included.
### Legacy URLs
`.htaccess` keeps the earlier addresses working with a 301:
| From | To |
| --- | --- |
| `/<atlas>/modules/xoonips/download.php?file_id=N` | `/<atlas>/modules/xoonips/file/N` |
| `/data/<atlas>/file/...` (v2.0.0) | `/<atlas>/modules/xoonips/file/...` |
| `/data/<atlas>/static/...` (v2.0.0) | `/<atlas>/static/...` |
| `/<atlas>/file/...` | `/<atlas>/modules/xoonips/file/...` |
## Google Analytics
`GOOGLE_ANALYTICS_TRACKING_ID` in `src/config.ts` carries the GA4 measurement
ID. Emptying it disables tracking.
+39
View File
@@ -0,0 +1,39 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.13/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!**/dist", "!src/common/assets"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 4,
"lineWidth": 120
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"semicolons": "always",
"trailingCommas": "es5",
"arrowParentheses": "always"
}
},
"linter": {
"enabled": true,
"rules": {
"preset": "recommended"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
@@ -25,6 +25,6 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+2098 -5586
View File
File diff suppressed because it is too large Load Diff
+41 -56
View File
@@ -1,58 +1,43 @@
{
"name": "brainatlas",
"private": true,
"version": "2.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write src/"
},
"dependencies": {
"@orrisroot/react-html-parser": "^2.1.1",
"async-lock": "^1.4.0",
"axios": "^1.4.0",
"lokijs": "^1.5.12",
"moment": "^2.29.4",
"node-stdlib-browser": "^1.2.0",
"rc-tree": "^5.7.9",
"react": "^18.2.0",
"react-app-polyfill": "^3.0.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-ga4": "^2.1.0",
"react-helmet-async": "^1.3.0",
"react-overlays": "^5.2.1",
"react-router-dom": "^6.14.2",
"react-router-hash-link": "^2.4.3",
"react-spinner-material": "^1.4.0",
"sanitize.css": "^13.0.0",
"xregexp": "^5.1.1",
"yet-another-react-lightbox": "^3.12.0"
},
"devDependencies": {
"@types/async-lock": "^1.4.0",
"@types/jest": "^29.5.3",
"@types/lokijs": "^1.5.8",
"@types/node": "^18.17.3",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@types/react-router-dom": "^5.3.3",
"@types/react-router-hash-link": "^2.4.6",
"@types/react-syntax-highlighter": "^15.5.7",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^8.46.0",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"prettier": "^3.0.1",
"typescript": "^5.1.6",
"vite": "^4.4.9",
"vite-plugin-node-stdlib-browser": "^0.2.1",
"vite-plugin-rewrite-all": "^1.0.1"
}
"name": "brainatlas",
"version": "2.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "biome lint",
"format": "biome format --write",
"check": "biome check --write"
},
"dependencies": {
"@dr.pogodin/react-helmet": "^3.2.3",
"@orrisroot/react-html-parser": "^3.0.1",
"async-lock": "^1.4.1",
"date-fns": "^4.4.0",
"ky": "^2.1.0",
"lokijs": "^1.5.12",
"rc-tree": "^5.13.1",
"react": "^19.3.0",
"react-cookie": "^8.1.2",
"react-dom": "^19.3.0",
"react-ga4": "^3.0.1",
"react-overlays": "^5.2.1",
"react-router": "^8.3.1",
"sanitize.css": "^13.0.0",
"yet-another-react-lightbox": "^3.32.2"
},
"devDependencies": {
"@biomejs/biome": "^2.5.13",
"@types/async-lock": "^1.4.2",
"@types/lokijs": "^1.5.14",
"@types/node": "^24.13.4",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.1",
"sirv": "^3.0.2",
"typescript": "~7.0.2",
"vite": "^8.3.0"
}
}
+10 -8
View File
@@ -2,20 +2,22 @@
RewriteEngine On
RewriteBase /
# XOOPS download URLs point at the file directory, whose own .htaccess serves the file.
RewriteCond %{QUERY_STRING} (^|&)file_id=([0-9]+)($|&)
RewriteRule ^degu/modules/xoonips/download.php /data/degu/file/%2? [R=301,L]
RewriteRule ^(degu|jm|marmoset)/modules/xoonips/download\.php$ /$1/modules/xoonips/file/%2? [R=301,L]
RewriteCond %{QUERY_STRING} (^|&)file_id=([0-9]+)($|&)
RewriteRule ^jm/modules/xoonips/download.php /data/jm/file/%2? [R=301,L]
RewriteCond %{QUERY_STRING} (^|&)file_id=([0-9]+)($|&)
RewriteRule ^marmoset/modules/xoonips/download.php /data/marmoset/file/%2? [R=301,L]
# The v2.0.0 layout served everything under /data/; keep those links alive.
RewriteRule ^data/(degu|jm|marmoset)/file/(.*)$ /$1/modules/xoonips/file/$2 [R=301,L]
RewriteRule ^data/(degu|jm|marmoset)/static/(.*)$ /$1/static/$2 [R=301,L]
# An older static layout put the files at /<atlas>/file/.
RewriteRule ^(degu|jm|marmoset)/file/(.*)$ /$1/modules/xoonips/file/$2 [R=301,L]
RewriteRule ^index\.html$ - [L]
# Anything that is not a real file goes to the SPA -- including the atlas
# directories themselves, which now exist on disk as data roots.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l [OR]
RewriteCond %{REQUEST_FILENAME} !-d [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_FILENAME}index.html !-f
RewriteRule . /index.html [L]
+13 -13
View File
@@ -1,15 +1,15 @@
{
"short_name": "BSI-NI Brain Atlas",
"name": "BSI-NI Brain Atlas",
"icons": [
{
"src": "favicon.ico",
"sizes": "32x32",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"short_name": "BSI-NI Brain Atlas",
"name": "BSI-NI Brain Atlas",
"icons": [
{
"src": "favicon.ico",
"sizes": "32x32",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+2 -2
View File
@@ -1,2 +1,2 @@
@import url('./common/assets/xoops.css');
@import url('./common/assets/theme/style.css');
@import url("./common/assets/xoops.css");
@import url("./common/assets/theme/style.css");
+47 -38
View File
@@ -1,57 +1,66 @@
import { HelmetProvider } from '@dr.pogodin/react-helmet';
import React from 'react';
import { CookiesProvider, useCookies } from 'react-cookie';
import ReactGA from 'react-ga4';
import { HelmetProvider } from 'react-helmet-async';
import { BrowserRouter, useLocation } from 'react-router-dom';
import { BrowserRouter, useLocation } from 'react-router';
import './App.css';
import AppRoot from './common/AppRoot';
import Config, { MultiLang } from './config';
import Config, { type MultiLang } from './config';
const isAnalyticsEnabled = import.meta.env.PROD && Config.GOOGLE_ANALYTICS_TRACKING_ID !== '';
if (isAnalyticsEnabled) {
// Page views are sent from the location effect in AppMain, the first one included;
// gtag's own page_view on config would count the landing page twice.
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID, { gtagOptions: { send_page_view: false } });
}
const mlang = ['en', 'ja'] as const;
const isMultiLang = (name: string): name is MultiLang => {
return mlang.some((value) => value === name);
return mlang.some((value) => value === name);
};
const AppMain: React.FC = () => {
const [cookies, setCookie] = useCookies();
const typedCookies = cookies as Record<string, string>;
const [lang, setLang] = React.useState<MultiLang>(
isMultiLang(typedCookies?.ml_lang ?? '') ? (cookies.ml_lang as MultiLang) : 'en',
);
const location = useLocation();
const [cookies, setCookie] = useCookies();
const typedCookies = cookies as Record<string, string>;
const [lang, setLang] = React.useState<MultiLang>(
isMultiLang(typedCookies?.ml_lang ?? '') ? (cookies.ml_lang as MultiLang) : 'en'
);
const location = useLocation();
React.useEffect(() => {
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);
}, [location, cookies, setCookie, lang, setLang]);
React.useEffect(() => {
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);
}, [location, cookies, setCookie, lang]);
return <AppRoot lang={lang} />;
React.useEffect(() => {
if (isAnalyticsEnabled) {
ReactGA.send({ hitType: 'pageview', page: location.pathname + location.search });
}
}, [location.pathname, location.search]);
return <AppRoot lang={lang} />;
};
const App: React.FC = () => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID.startsWith('G-')) {
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID);
}
return (
<BrowserRouter>
<CookiesProvider>
<HelmetProvider>
<AppMain />
</HelmetProvider>
</CookiesProvider>
</BrowserRouter>
);
return (
<BrowserRouter>
<CookiesProvider>
<HelmetProvider>
<AppMain />
</HelmetProvider>
</CookiesProvider>
</BrowserRouter>
);
};
export default App;
+52 -64
View File
@@ -1,84 +1,72 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { Route, Routes, useLocation } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../config';
import { Helmet } from '@dr.pogodin/react-helmet';
import type React from 'react';
import { Route, Routes, useLocation } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../config';
import SiteIndex from '../custom/SiteIndex';
import Functions from '../functions';
import Footer from './Footer';
import Header from './Header';
import MainContent from './MainContent';
import PageNotFound from './lib/PageNotFound';
import MainContent from './MainContent';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
interface PropsMain extends Props {
type: BrainAtlasType;
isHtml: boolean;
isLink: boolean;
type: BrainAtlasType;
isHtml: boolean;
isLink: boolean;
}
const PageMain: React.FC<PropsMain> = (props) => {
const { lang, type, isHtml, isLink } = props;
return (
<>
<Header lang={lang} type={type} />
<div id="main">
<MainContent lang={lang} type={type} isHtml={isHtml} isLink={isLink} />
</div>
<Footer lang={lang} type={type} isLink={isLink} />
</>
);
const { lang, type, isHtml, isLink } = props;
return (
<>
<Header lang={lang} type={type} />
<div id="main">
<MainContent lang={lang} type={type} isHtml={isHtml} isLink={isLink} />
</div>
<Footer lang={lang} type={type} isLink={isLink} />
</>
);
};
const AppRoot: React.FC<Props> = (props) => {
const { lang } = props;
const location = useLocation();
const match = location.pathname.match(
/^\/(degu|jm|marmoset)(?:\/|$|(?:_(html)(?:\/$|$|\/(link\.html$))))/,
);
const isHtml = match !== null ? typeof match[2] !== 'undefined' : false;
const isLink = match !== null ? typeof match[3] !== 'undefined' : false;
const { lang } = props;
const location = useLocation();
const match = location.pathname.match(/^\/(degu|jm|marmoset)(?:\/|$|(?:_(html)(?:\/$|$|\/(link\.html$))))/);
const isHtml = match !== null ? typeof match[2] !== 'undefined' : false;
const isLink = match !== null ? typeof match[3] !== 'undefined' : false;
return (
<div id="page">
<Helmet>
<title>
{Functions.siteTitle(lang)} - {Functions.siteSlogan(lang)}
</title>
</Helmet>
<Routes>
<Route index element={<SiteIndex lang={lang} />} />
<Route
path="degu_html/*"
element={<PageMain lang={lang} type="degu" isHtml={isHtml} isLink={isLink} />}
/>
<Route
path="jm_html/*"
element={<PageMain lang={lang} type="jm" isHtml={isHtml} isLink={isLink} />}
/>
<Route
path="marmoset_html/*"
element={<PageMain lang={lang} type="marmoset" isHtml={isHtml} isLink={isLink} />}
/>
<Route
path="degu/*"
element={<PageMain lang={lang} type="degu" isHtml={isHtml} isLink={isLink} />}
/>
<Route
path="jm/*"
element={<PageMain lang={lang} type="jm" isHtml={isHtml} isLink={isLink} />}
/>
<Route
path="marmoset/*"
element={<PageMain lang={lang} type="marmoset" isHtml={isHtml} isLink={isLink} />}
/>
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
return (
<div id="page">
<Helmet>
<title>
{Functions.siteTitle(lang)} - {Functions.siteSlogan(lang)}
</title>
</Helmet>
<Routes>
<Route index element={<SiteIndex lang={lang} />} />
<Route
path="degu_html/*"
element={<PageMain lang={lang} type="degu" isHtml={isHtml} isLink={isLink} />}
/>
<Route path="jm_html/*" element={<PageMain lang={lang} type="jm" isHtml={isHtml} isLink={isLink} />} />
<Route
path="marmoset_html/*"
element={<PageMain lang={lang} type="marmoset" isHtml={isHtml} isLink={isLink} />}
/>
<Route path="degu/*" element={<PageMain lang={lang} type="degu" isHtml={isHtml} isLink={isLink} />} />
<Route path="jm/*" element={<PageMain lang={lang} type="jm" isHtml={isHtml} isLink={isLink} />} />
<Route
path="marmoset/*"
element={<PageMain lang={lang} type="marmoset" isHtml={isHtml} isLink={isLink} />}
/>
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
};
export default AppRoot;
+36 -41
View File
@@ -1,50 +1,45 @@
import React from 'react';
import type React from 'react';
import { Link } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../config';
import { Link } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../config';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
isLink: boolean;
lang: MultiLang;
type: BrainAtlasType;
isLink: boolean;
}
const Footer: React.FC<Props> = (props) => {
const { type, isLink } = props;
return (
<footer>
<div className="links clearfix">
<span className={`image ${type}`}>&nbsp;</span>
{!isLink && (
<Link className="relatedLink" to={'/' + type + '_html/link.html'}>
<span>Related Link</span>
</Link>
)}
<a
className="bsi-ni"
href="https://bsi-ni.brain.riken.jp/"
target="_blank"
rel="noopener noreferrer"
>
<span>BSI-Neuroinformatics</span>
</a>
<span className="gotoTop">
<Link to="/">&raquo; GO TO TOP</Link>
</span>
</div>
{type === 'marmoset' ? (
<div className="copyright">
Copyright (C) 2018 Laboratory for Symbolic Cognitive Development, RIKEN Center for
Biosystems Dynamics Research.
</div>
) : (
<div className="copyright">
Copyright (C) 2017 BSI-NI Project &amp; Laboratory for Symbolic Cognitive Development,
RIKEN Brain Science Insititute.
</div>
)}
</footer>
);
const { type, isLink } = props;
return (
<footer>
<div className="links clearfix">
<span className={`image ${type}`}>&nbsp;</span>
{!isLink && (
<Link className="relatedLink" to={`/${type}_html/link.html`}>
<span>Related Link</span>
</Link>
)}
<a className="bsi-ni" href="https://bsi-ni.brain.riken.jp/" target="_blank" rel="noopener noreferrer">
<span>BSI-Neuroinformatics</span>
</a>
<span className="gotoTop">
<Link to="/">&raquo; GO TO TOP</Link>
</span>
</div>
{type === 'marmoset' ? (
<div className="copyright">
Copyright (C) 2018 Laboratory for Symbolic Cognitive Development, RIKEN Center for Biosystems
Dynamics Research.
</div>
) : (
<div className="copyright">
Copyright (C) 2017 BSI-NI Project &amp; Laboratory for Symbolic Cognitive Development, RIKEN Brain
Science Insititute.
</div>
)}
</footer>
);
};
export default Footer;
+72 -73
View File
@@ -1,93 +1,92 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { Link, useLocation } from 'react-router-dom';
import Config, { BrainAtlasType, MultiLang } from '../config';
import { Helmet } from '@dr.pogodin/react-helmet';
import type React from 'react';
import { Link, useLocation } from 'react-router';
import Config, { type BrainAtlasType, type MultiLang } from '../config';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
type HeaderType = BrainAtlasType | 'marmoset_3d' | 'marmoset_std';
const titles = {
degu: 'Degu - 3D Brain Atlas',
jm: 'Japanese Macaque Monkey - the MRI Standard Brain',
marmoset: 'Marmoset',
marmoset_3d: 'Marmoset - 3D Brain Atlas',
marmoset_std: 'Marmoset - the MRI Standard Brain',
degu: 'Degu - 3D Brain Atlas',
jm: 'Japanese Macaque Monkey - the MRI Standard Brain',
marmoset: 'Marmoset',
marmoset_3d: 'Marmoset - 3D Brain Atlas',
marmoset_std: 'Marmoset - the MRI Standard Brain',
};
const urls = {
degu: '/degu/modules/xoonips/listitem.php?index_id=24',
jm: '/jm/modules/xoonips/listitem.php?index_id=9',
marmoset: '/marmoset_html/',
marmoset_3d: '/marmoset/modules/xoonips/listitem.php?index_id=66',
marmoset_std: '/marmoset/modules/xoonips/listitem.php?index_id=71',
degu: '/degu/modules/xoonips/listitem.php?index_id=24',
jm: '/jm/modules/xoonips/listitem.php?index_id=9',
marmoset: '/marmoset_html/',
marmoset_3d: '/marmoset/modules/xoonips/listitem.php?index_id=66',
marmoset_std: '/marmoset/modules/xoonips/listitem.php?index_id=71',
};
const getHeaderType = (type: BrainAtlasType, pathname: string, search: string): HeaderType => {
if (type === 'marmoset') {
const params = new URLSearchParams(search);
switch (pathname) {
case '/marmoset/modules/xoonips/listitem.php': {
const index_id = params.get('index_id') ?? '3';
if (['66', '69'].includes(index_id)) {
return 'marmoset_3d';
if (type === 'marmoset') {
const params = new URLSearchParams(search);
switch (pathname) {
case '/marmoset/modules/xoonips/listitem.php': {
const index_id = params.get('index_id') ?? '3';
if (['66', '69'].includes(index_id)) {
return 'marmoset_3d';
}
if (['71', '73'].includes(index_id)) {
return 'marmoset_std';
}
break;
}
case '/marmoset/modules/xoonips/detail.php': {
const item_id = params.get('item_id') ?? '';
if (['77', '75', '79', '80'].includes(item_id)) {
return 'marmoset_3d';
}
if (['72'].includes(item_id)) {
return 'marmoset_std';
}
const id = params.get('id') ?? '';
if (['Marmoset02', 'Marmoset04', 'Marmoset05', 'Marmoset06'].includes(id)) {
return 'marmoset_3d';
}
if (['004'].includes(id)) {
return 'marmoset_std';
}
break;
}
}
if (['71', '73'].includes(index_id)) {
return 'marmoset_std';
}
break;
}
case '/marmoset/modules/xoonips/detail.php': {
const item_id = params.get('item_id') ?? '';
if (['77', '75', '79', '80'].includes(item_id)) {
return 'marmoset_3d';
}
if (['72'].includes(item_id)) {
return 'marmoset_std';
}
const id = params.get('id') ?? '';
if (['Marmoset02', 'Marmoset04', 'Marmoset05', 'Marmoset06'].includes(id)) {
return 'marmoset_3d';
}
if (['004'].includes(id)) {
return 'marmoset_std';
}
break;
}
}
}
return type;
return type;
};
const Header: React.FC<Props> = (props) => {
const { lang, type } = props;
const location = useLocation();
const { pathname, search } = location;
const params = new URLSearchParams(search);
params.set('ml_lang', lang === 'en' ? 'ja' : 'en');
const langUrl = `${location.pathname}?${params.toString()}`;
const langLabel = lang === 'en' ? 'To Japanese' : 'To English';
const hType = getHeaderType(type, pathname, search);
const title = Config.SITE_TITLE + ' ' + titles[hType];
const url = urls[hType];
return (
<header className={hType}>
<Helmet>
<title>{title}</title>
</Helmet>
<div className="selLang">
<Link to={langUrl}>{langLabel}</Link>
</div>
<Link to={url} title={title}>
<div className="title">
<span>{title}</span>
</div>
</Link>
</header>
);
const { lang, type } = props;
const location = useLocation();
const { pathname, search } = location;
const params = new URLSearchParams(search);
params.set('ml_lang', lang === 'en' ? 'ja' : 'en');
const langUrl = `${location.pathname}?${params.toString()}`;
const langLabel = lang === 'en' ? 'To Japanese' : 'To English';
const hType = getHeaderType(type, pathname, search);
const title = `${Config.SITE_TITLE} ${titles[hType]}`;
const url = urls[hType];
return (
<header className={hType}>
<Helmet>
<title>{title}</title>
</Helmet>
<div className="selLang">
<Link to={langUrl}>{langLabel}</Link>
</div>
<Link to={url} title={title}>
<div className="title">
<span>{title}</span>
</div>
</Link>
</header>
);
};
export default Header;
+25 -25
View File
@@ -1,7 +1,7 @@
import React from 'react';
import type React from 'react';
import { Route, Routes } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../config';
import { Route, Routes } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../config';
import CoverPage from '../custom/CoverPage';
import RelatedLink from '../custom/RelatedLink';
import Xoonips from '../xoonips/Xoonips';
@@ -9,31 +9,31 @@ import XoonipsTop from '../xoonips/XoonipsTop';
import XoopsPathRedirect from './XoopsPathRedirect';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
isHtml: boolean;
isLink: boolean;
lang: MultiLang;
type: BrainAtlasType;
isHtml: boolean;
isLink: boolean;
}
const MainContent: React.FC<Props> = (props) => {
const { lang, type, isHtml, isLink } = props;
return (
<div className="mainContent">
{isHtml ? (
isLink ? (
<RelatedLink lang={lang} type={type} />
) : (
<CoverPage lang={lang} type={type} />
)
) : (
<Routes>
<Route index element={<XoonipsTop lang={lang} type={type} />} />
<Route path="modules/xoonips/*" element={<Xoonips lang={lang} type={type} />} />
<Route path="*" element={<XoopsPathRedirect lang={lang} />} />
</Routes>
)}
</div>
);
const { lang, type, isHtml, isLink } = props;
return (
<div className="mainContent">
{isHtml ? (
isLink ? (
<RelatedLink lang={lang} type={type} />
) : (
<CoverPage lang={lang} type={type} />
)
) : (
<Routes>
<Route index element={<XoonipsTop lang={lang} type={type} />} />
<Route path="modules/xoonips/*" element={<Xoonips lang={lang} type={type} />} />
<Route path="*" element={<XoopsPathRedirect lang={lang} />} />
</Routes>
)}
</div>
);
};
export default MainContent;
+19 -19
View File
@@ -1,30 +1,30 @@
import React from 'react';
import type React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { MultiLang } from '../config';
import { Navigate, useLocation } from 'react-router';
import type { MultiLang } from '../config';
import PageNotFound from './lib/PageNotFound';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoopsPathRedirect: React.FC<Props> = (props) => {
const { lang } = props;
const location = useLocation();
const { pathname } = location;
const getRedirectUrl = () => {
switch (pathname || '') {
case '/index.php': {
return '/';
}
const { lang } = props;
const location = useLocation();
const { pathname } = location;
const getRedirectUrl = () => {
switch (pathname || '') {
case '/index.php': {
return '/';
}
}
return '';
};
const url = getRedirectUrl();
if (url === '') {
return <PageNotFound lang={lang} />;
}
return '';
};
const url = getRedirectUrl();
if (url === '') {
return <PageNotFound lang={lang} />;
}
return <Navigate to={url} />;
return <Navigate to={url} />;
};
export default XoopsPathRedirect;
+53
View File
@@ -0,0 +1,53 @@
import type { ComponentProps, MouseEvent } from 'react';
import { Link, type To } from 'react-router';
type LinkProps = ComponentProps<typeof Link>;
const RETRY_INTERVAL_MS = 16;
const RETRY_COUNT = 20;
const hashOf = (to: To): string => {
if (typeof to === 'string') {
const idx = to.indexOf('#');
return idx === -1 ? '' : to.slice(idx + 1);
}
return (to.hash ?? '').replace(/^#/, '');
};
const scrollToHash = (hash: string, remaining = RETRY_COUNT) => {
if (hash === '') {
return;
}
const id = decodeURIComponent(hash);
const target = document.getElementById(id) ?? document.querySelector(`[name="${CSS.escape(id)}"]`);
if (target !== null) {
target.scrollIntoView();
return;
}
// The target may not be in the DOM yet (pico pages fetch their content).
if (remaining > 0) {
setTimeout(() => {
scrollToHash(hash, remaining - 1);
}, RETRY_INTERVAL_MS);
}
};
/**
* A `<Link>` that also scrolls to the element named by the URL fragment.
* Replaces `react-router-hash-link`, which is unmaintained and still targets react-router v5.
*/
const HashLink = ({ onClick, to, ...rest }: LinkProps) => {
const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {
onClick?.(e);
if (e.defaultPrevented) {
return;
}
// Let react-router commit the navigation before looking for the target.
setTimeout(() => {
scrollToHash(hashOf(to));
}, 0);
};
return <Link {...rest} to={to} onClick={handleClick} />;
};
export default HashLink;
-48
View File
@@ -1,48 +0,0 @@
import React from 'react';
import { Link } from 'react-router-dom';
interface Props {
url: string;
title: string;
image: string;
imageHover?: string;
}
const LinkImage: React.FC<Props> = (props) => {
const { url, title, image: imageNormal, imageHover } = props;
const [image, setImage] = React.useState<string>(imageNormal);
const handleMouseOver = () => {
if (imageHover != null) {
setImage(imageHover);
}
};
const handleMouseOut = () => {
if (imageHover != null) {
setImage(imageNormal);
}
};
if (url.match(/^(\/|\.)/) === null) {
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
>
{image}
</a>
);
}
return (
<Link to={url} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
<img style={{ verticalAlign: 'middle' }} src={image} alt={title} title={title} />
</Link>
);
};
export default LinkImage;
+27
View File
@@ -0,0 +1,27 @@
.container {
display: flex;
justify-content: center;
align-content: center;
margin: 100px 0;
}
.spinner {
width: 120px;
height: 120px;
border: 8px solid #eeeeee;
border-top-color: #cccccc;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation-duration: 4s;
}
}
+5 -16
View File
@@ -1,20 +1,9 @@
import React from 'react';
import styles from './Loading.module.css';
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} />
const Loading = () => (
<div className={styles.container}>
<div className={styles.spinner} role="status" aria-label="Loading" />
</div>
);
};
);
export default Loading;
+7 -7
View File
@@ -1,17 +1,17 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../config';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const NoticeSiteHasBeenArchived: React.FC<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;
+20 -21
View File
@@ -1,31 +1,30 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { MultiLang } from '../../config';
import { Helmet } from '@dr.pogodin/react-helmet';
import type React from 'react';
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 url = '/';
const { lang } = props;
const url = '/';
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&apos;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&apos;t exist.</p>
<p>
If the page does not automatically reload, please go to the <a href={url}>top page</a>
</p>
</section>
</div>
);
};
export default PageNotFound;
+62 -63
View File
@@ -1,10 +1,9 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../config';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import imageRank2 from '../assets/images/rank3dbf8e94a6f72.gif';
import imageRank3 from '../assets/images/rank3dbf8e9e7d88d.gif';
import imageRank2 from '../assets/images/rank3dbf8e94a6f72.gif';
import imageRank4 from '../assets/images/rank3dbf8ea81e642.gif';
import imageRank5 from '../assets/images/rank3dbf8eb1a72e7.gif';
import imageRank6 from '../assets/images/rank3dbf8edf15093.gif';
@@ -12,71 +11,71 @@ import imageRank7 from '../assets/images/rank3dbf8ee8681cd.gif';
import imageRank1 from '../assets/images/rank3e632f95e81ca.gif';
interface Props {
lang: MultiLang;
rank: number;
posts: number;
lang: MultiLang;
rank: number;
posts: number;
}
const userRanks = [
{
title: '[en]Just popping in[/en][ja]新米[/ja]',
min: 0,
max: 20,
special: false,
image: imageRank1,
},
{
title: '[en]Not too shy to talk[/en][ja]半人前[/ja]',
min: 21,
max: 40,
special: false,
image: imageRank2,
},
{
title: '[en]Quite a regular[/en][ja]常連[/ja]',
min: 41,
max: 70,
special: false,
image: imageRank3,
},
{
title: "[en]Just can't stay away[/en][ja]一人前[/ja]",
min: 71,
max: 150,
special: false,
image: imageRank4,
},
{
title: '[en]Home away from home[/en][ja]長老[/ja]',
min: 151,
max: 10000,
special: false,
image: imageRank5,
},
{
title: '[en]Moderator[/en][ja]モデレータ[/ja]',
min: 0,
max: 0,
special: true,
image: imageRank6,
},
{ title: '[en]Webmaster[/en][ja]管理人[/ja]', min: 0, max: 0, special: true, image: imageRank7 },
{
title: '[en]Just popping in[/en][ja]新米[/ja]',
min: 0,
max: 20,
special: false,
image: imageRank1,
},
{
title: '[en]Not too shy to talk[/en][ja]半人前[/ja]',
min: 21,
max: 40,
special: false,
image: imageRank2,
},
{
title: '[en]Quite a regular[/en][ja]常連[/ja]',
min: 41,
max: 70,
special: false,
image: imageRank3,
},
{
title: "[en]Just can't stay away[/en][ja]一人前[/ja]",
min: 71,
max: 150,
special: false,
image: imageRank4,
},
{
title: '[en]Home away from home[/en][ja]長老[/ja]',
min: 151,
max: 10000,
special: false,
image: imageRank5,
},
{
title: '[en]Moderator[/en][ja]モデレータ[/ja]',
min: 0,
max: 0,
special: true,
image: imageRank6,
},
{ title: '[en]Webmaster[/en][ja]管理人[/ja]', min: 0, max: 0, special: true, image: imageRank7 },
];
const UserRankStarImage: React.FC<Props> = (props) => {
const { lang, rank, posts } = props;
const findRank = (posts: number) => {
const rank = userRanks.find((userRank) => {
if (posts >= userRank.min && posts <= userRank.max) {
return true;
}
return false;
});
return typeof rank !== 'undefined' ? rank : userRanks[0];
};
const userRank = rank > 0 && rank <= 7 ? userRanks[rank - 1] : findRank(posts);
const title = Functions.mlang(userRank.title, lang);
return <img src={userRank.image} alt={title} title={title} />;
const { lang, rank, posts } = props;
const findRank = (posts: number) => {
const rank = userRanks.find((userRank) => {
if (posts >= userRank.min && posts <= userRank.max) {
return true;
}
return false;
});
return typeof rank !== 'undefined' ? rank : userRanks[0];
};
const userRank = rank > 0 && rank <= 7 ? userRanks[rank - 1] : findRank(posts);
const title = Functions.mlang(userRank.title, lang);
return <img src={userRank.image} alt={title} title={title} />;
};
export default UserRankStarImage;
+117 -124
View File
@@ -1,160 +1,153 @@
import React from 'react';
import ReactHtmlParser, {
convertNodeToElement,
DomElement,
DomNode,
Transform,
convertNodeToElement,
type DomElement,
type DomNode,
type Transform,
} from '@orrisroot/react-html-parser';
import { ElementType } from 'htmlparser2';
import { HashLink } from 'react-router-hash-link';
import { MultiLang } from '../../config';
import type React from 'react';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import HashLink from './HashLink';
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, (_: string, m1: string) => {
return '[code]' + Functions.base64Encode(m1) + '[/code]';
});
}
return text;
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (_: string, m1: string) => {
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, (_: string, m1: string) => {
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, (_: string, m1: string) => {
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
if (doimage) {
// TODO: implement
if (doimage) {
return text;
}
return text;
}
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): Record<string, string> => {
const ret: Record<string, string> = {};
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: Record<string, string> = {};
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 === ElementType.Tag) {
const node_ = node as DomElement;
if (node_.name === 'a') {
const url = node_.attribs?.href ?? '/';
const download = node_.attribs?.download ?? '';
const rel = node_.attribs?.rel ?? '';
const className = node_.attribs?.class ?? '';
const isFile = download !== '' || /\.(zip|pdf|png|gif|jpg)$/.test(url);
const isExternal =
/external/.test(rel) || /external/.test(className) || /^(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>
);
}
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?.download ?? '';
const rel = node_.attribs?.rel ?? '';
const className = node_.attribs?.class ?? '';
const isFile = download !== '' || /\.(zip|pdf|png|gif|jpg)$/.test(url);
const isExternal = /external/.test(rel) || /external/.test(className) || /^(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?.src ?? '';
node_.attribs.src = src.replace('`XOOPS_URL`', '/');
return convertNodeToElement(node_, index, transform);
}
}
if (node_.name === 'img') {
const src = node_.attribs?.src ?? '';
node_.attribs.src = src.replace('`XOOPS_URL`', '/');
return convertNodeToElement(node_, index, transform);
}
}
};
const XoopsCode: React.FC<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;
+11 -5
View File
@@ -1,18 +1,24 @@
const SITE_TITLE = 'BSI-NI Brain Atlas';
const SITE_SLOGAN = 'Welcome to BRAIN ATLAS';
const GOOGLE_ANALYTICS_TRACKING_ID = '';
const GOOGLE_ANALYTICS_TRACKING_ID = 'G-C186H597TK';
const XOONIPS_ITEMTYPES = ['files', 'url', 'paper', 'book'];
// Vite's BASE_URL always carries a trailing slash ('/' when the app is served
// from the document root). Strip it so it can be concatenated the same way
// CRA's Config.BASE_URL was.
const BASE_URL = import.meta.env.BASE_URL.replace(/\/$/, '');
export type MultiLang = 'en' | 'ja';
export type BrainAtlasType = 'degu' | 'jm' | 'marmoset';
export const BrainAtlasTypes: readonly BrainAtlasType[] = ['degu', 'jm', 'marmoset'];
const Config = {
SITE_TITLE,
SITE_SLOGAN,
GOOGLE_ANALYTICS_TRACKING_ID,
XOONIPS_ITEMTYPES,
SITE_TITLE,
SITE_SLOGAN,
GOOGLE_ANALYTICS_TRACKING_ID,
XOONIPS_ITEMTYPES,
BASE_URL,
};
export default Config;
+7 -7
View File
@@ -1,17 +1,17 @@
.coverPage {
margin: 20px;
margin: 20px;
}
.coverPage .links {
list-style-type: square;
list-style-type: square;
}
.coverPage .links li {
line-height: 2;
line-height: 2;
}
.coverPage .links li a {
font-size: 1.424em;
font-weight: bold;
color: #378893;
font-size: 1.424em;
font-weight: bold;
color: #378893;
}
.coverPage .links li a:hover {
color: #62c2c2;
color: #62c2c2;
}
+27 -27
View File
@@ -1,39 +1,39 @@
import React from 'react';
import type React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import PageNotFound from '../common/lib/PageNotFound';
import { BrainAtlasType, MultiLang } from '../config';
import type { BrainAtlasType, MultiLang } from '../config';
import styles from './CoverPage.module.css';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const CoverPage: React.FC<Props> = (props) => {
const { lang, type } = props;
const links = [
{ title: 'Marmoset-3D Brain Atlas', url: '/marmoset/modules/xoonips/listitem.php?index_id=66' },
{
title: 'Marmoset-the MRI Standard Brain',
url: '/marmoset/modules/xoonips/listitem.php?index_id=71',
},
];
if (type !== 'marmoset') {
return <PageNotFound lang={lang} />;
}
return (
<div className={styles.coverPage}>
<ul className={styles.links}>
{links.map((link) => (
<li key={link.title}>
<Link to={link.url}>{link.title}</Link>
</li>
))}
</ul>
</div>
);
const { lang, type } = props;
const links = [
{ title: 'Marmoset-3D Brain Atlas', url: '/marmoset/modules/xoonips/listitem.php?index_id=66' },
{
title: 'Marmoset-the MRI Standard Brain',
url: '/marmoset/modules/xoonips/listitem.php?index_id=71',
},
];
if (type !== 'marmoset') {
return <PageNotFound lang={lang} />;
}
return (
<div className={styles.coverPage}>
<ul className={styles.links}>
{links.map((link) => (
<li key={link.title}>
<Link to={link.url}>{link.title}</Link>
</li>
))}
</ul>
</div>
);
};
export default CoverPage;
+9 -9
View File
@@ -1,21 +1,21 @@
.relatedLink {
margin: 20px;
margin: 20px;
}
.relatedLink .title {
height: 57px;
width: 168px;
background-image: url(../common/assets/theme/images/related_link.png);
height: 57px;
width: 168px;
background-image: url(../common/assets/theme/images/related_link.png);
}
.relatedLink .title span {
display: none;
display: none;
}
.relatedLink .links li {
line-height: 2;
line-height: 2;
}
.relatedLink .links li a {
font-size: 95%;
color: #333333;
font-size: 95%;
color: #333333;
}
.relatedLink .links li a:hover {
color: #62c2c2;
color: #62c2c2;
}
+39 -39
View File
@@ -1,50 +1,50 @@
import React from 'react';
import type React from 'react';
import { BrainAtlasType, MultiLang } from '../config';
import type { BrainAtlasType, MultiLang } from '../config';
import styles from './RelatedLink.module.css';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const RelatedLink: React.FC<Props> = (props) => {
const { type } = props;
const links = {
degu: [{ title: 'RIKEN BSI', url: 'http://www.brain.riken.jp/' }],
jm: [{ title: 'RIKEN BSI', url: 'http://www.brain.riken.jp/' }],
marmoset: [
{ title: 'RIKEN BSI', url: 'http://www.brain.riken.jp/' },
{
title: 'Keio-RIKEN Research centre for Human Cognition',
url: 'http://human-cognition.keio.ac.jp/english/',
},
{ title: 'FIRST project', url: 'http://www.brain.riken.jp/first-okano/en/index.html' },
{
title: 'Press release',
url: 'http://www.brain.riken.jp/first-okano/en/achievements/2010/papers.html',
},
],
};
return (
<div className={styles.relatedLink}>
<div className={styles.title}>
<span>Related Link</span>
</div>
<ul className={styles.links}>
{links[type].map((link) => {
return (
<li key={link.title}>
<a href={link.url} target="_blank" rel="noopener noreferrer">
{link.title}
</a>
</li>
);
})}
</ul>
</div>
);
const { type } = props;
const links = {
degu: [{ title: 'RIKEN BSI', url: 'http://www.brain.riken.jp/' }],
jm: [{ title: 'RIKEN BSI', url: 'http://www.brain.riken.jp/' }],
marmoset: [
{ title: 'RIKEN BSI', url: 'http://www.brain.riken.jp/' },
{
title: 'Keio-RIKEN Research centre for Human Cognition',
url: 'http://human-cognition.keio.ac.jp/english/',
},
{ title: 'FIRST project', url: 'http://www.brain.riken.jp/first-okano/en/index.html' },
{
title: 'Press release',
url: 'http://www.brain.riken.jp/first-okano/en/achievements/2010/papers.html',
},
],
};
return (
<div className={styles.relatedLink}>
<div className={styles.title}>
<span>Related Link</span>
</div>
<ul className={styles.links}>
{links[type].map((link) => {
return (
<li key={link.title}>
<a href={link.url} target="_blank" rel="noopener noreferrer">
{link.title}
</a>
</li>
);
})}
</ul>
</div>
);
};
export default RelatedLink;
+41 -41
View File
@@ -1,65 +1,65 @@
.siteIndex {
min-width: 700px;
height: 100%;
width: 100%;
background-color: #9acae4;
min-width: 700px;
height: 100%;
width: 100%;
background-color: #9acae4;
}
.siteIndex header {
height: 25px;
background-color: #51bb9d;
border-bottom: 1px white solid;
height: 25px;
background-color: #51bb9d;
border-bottom: 1px white solid;
}
.siteIndex .title {
position: relative;
margin: 50px auto 25px;
width: 476px;
height: 32px;
background-image: url(assets/images/index_title.png);
position: relative;
margin: 50px auto 25px;
width: 476px;
height: 32px;
background-image: url(assets/images/index_title.png);
}
.siteIndex .title span {
display: none;
display: none;
}
.siteIndex .notice {
text-align: center;
height: 50px;
text-align: center;
height: 50px;
}
.siteIndex .image {
float: left;
width: 50%;
height: 500px;
background: url(assets/images/index_image.png) no-repeat top right;
float: left;
width: 50%;
height: 500px;
background: url(assets/images/index_image.png) no-repeat top right;
}
.siteIndex .links {
float: right;
width: 50%;
height: 500px;
}
.siteIndex .links ul {
margin: 130px 20px;
list-style-type: none;
}
.siteIndex .links ul li {
line-height: 50px;
}
.siteIndex .links ul li a {
display: inline-block;
height: 22px;
width: 250px;
float: right;
width: 50%;
height: 500px;
}
.siteIndex .degu a {
background: url(assets/images/index_link_degu.png) no-repeat;
background: url(assets/images/index_link_degu.png) no-repeat;
}
.siteIndex .jm a {
background: url(assets/images/index_link_jm.png) no-repeat;
background: url(assets/images/index_link_jm.png) no-repeat;
}
.siteIndex .marmoset a {
background: url(assets/images/index_link_marmoset.png) no-repeat;
background: url(assets/images/index_link_marmoset.png) no-repeat;
}
.siteIndex .links ul {
margin: 130px 20px;
list-style-type: none;
}
.siteIndex .links ul li {
line-height: 50px;
}
.siteIndex .links ul li a {
display: inline-block;
height: 22px;
width: 250px;
}
.siteIndex .links ul li span {
display: none;
display: none;
}
.siteIndex footer {
color: yellow;
font-weight: 500;
text-align: center;
color: yellow;
font-weight: 500;
text-align: center;
}
+38 -41
View File
@@ -1,54 +1,51 @@
import React from 'react';
import type React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import NoticeSiteHasBeenArchived from '../common/lib/NoticeSiteHasBeenArchived';
import { MultiLang } from '../config';
import type { MultiLang } from '../config';
import styles from './SiteIndex.module.css';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const SiteIndex: React.FC<Props> = (props) => {
const { lang } = props;
return (
<div className={styles.siteIndex}>
<header></header>
<div className={styles.title}>
<span>Welcome to BRAIN ATLAS</span>
</div>
<div className={styles.notice}>
<NoticeSiteHasBeenArchived lang={lang} />
</div>
<div className="clearfix">
<div className={styles.image}></div>
<div className={styles.links}>
<ul>
<li className={styles.degu}>
<Link to="/degu/modules/xoonips/listitem.php?index_id=24" title="Degu">
<span>Degu</span>
</Link>
</li>
<li className={styles.jm}>
<Link
to="/jm/modules/xoonips/listitem.php?index_id=9"
title="Japanese Macaque Monkey"
>
<span>Japanese Macaque Monkey</span>
</Link>
</li>
<li className={styles.marmoset}>
<Link to="/marmoset_html/" title="Common Marmoset">
<span>Common Marmoset</span>
</Link>
</li>
</ul>
const { lang } = props;
return (
<div className={styles.siteIndex}>
<header></header>
<div className={styles.title}>
<span>Welcome to BRAIN ATLAS</span>
</div>
<div className={styles.notice}>
<NoticeSiteHasBeenArchived lang={lang} />
</div>
<div className="clearfix">
<div className={styles.image}></div>
<div className={styles.links}>
<ul>
<li className={styles.degu}>
<Link to="/degu/modules/xoonips/listitem.php?index_id=24" title="Degu">
<span>Degu</span>
</Link>
</li>
<li className={styles.jm}>
<Link to="/jm/modules/xoonips/listitem.php?index_id=9" title="Japanese Macaque Monkey">
<span>Japanese Macaque Monkey</span>
</Link>
</li>
<li className={styles.marmoset}>
<Link to="/marmoset_html/" title="Common Marmoset">
<span>Common Marmoset</span>
</Link>
</li>
</ul>
</div>
</div>
<footer>BSI-NI &amp; Laboratory for Symbolic Cognitive Development</footer>
</div>
</div>
<footer>BSI-NI &amp; Laboratory for Symbolic Cognitive Development</footer>
</div>
);
);
};
export default SiteIndex;
+95 -108
View File
@@ -1,149 +1,136 @@
import XRegExp from 'xregexp';
import Config, { MultiLang } from './config';
import Config, { type MultiLang } from './config';
const escape = (str: string) => {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
return '%' + c.charCodeAt(0).toString(16);
});
const escapeUrl = (str: string) => {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
return `%${c.charCodeAt(0).toString(16)}`;
});
};
const unescape = (str: string) => {
return decodeURIComponent(str);
const unescapeUrl = (str: string) => {
return decodeURIComponent(str);
};
const htmlspecialchars = (str: 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) => {
return str.replace(/[-_](.)/g, (...matches) => {
return (matches[1] as string).toUpperCase();
});
return str.replace(/[-_](.)/g, (...matches) => {
return (matches[1] as string).toUpperCase();
});
};
const snakeCase = (str: string) => {
const 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) => {
const 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) => {
return btoa(unescape(encodeURIComponent(str)));
return btoa(unescape(encodeURIComponent(str)));
};
const base64Decode = (str: string) => {
return decodeURIComponent(escape(atob(str)));
return decodeURIComponent(escape(atob(str)));
};
const ordinal = (num: number) => {
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) => {
const mlLangs = ['en', 'ja'];
// escape brackets inside of <input type="text" value="...">
let text = str.replace(
/(<input)([^>]*)(>)/gis,
(whole: string, m1: string, m2: string, m3: string) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
const mlLangs = ['en', 'ja'];
// escape brackets inside of <input type="text" value="...">
let text = str.replace(/(<input)([^>]*)(>)/gis, (whole: string, m1: string, m2: string, m3: string) => {
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, (_, m1: string, m2: string, m3: string) => {
return m1 + m2.replace(/\[/g, '__ml[ml__') + m3;
}
return whole;
},
);
// escape brackets inside of <textarea></textarea>
text = text.replace(
/(<textarea[^>]*>)(.*)(<\/textarea>)/gis,
(_, m1: string, m2: string, m3: string) => {
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,
(_, m1: string, m2: string, m3: string) => {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
},
);
// unescape brackets inside of <input type="text" value="...">
text = text.replace(
/(<input)([^>]*)(>)/gis,
(whole: string, m1: string, m2: string, m3: string) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
});
// 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 = new RegExp(
'\\[(?:[^/][^\\]]+\\|)?' +
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, (_, m1: string, m2: string, m3: string) => {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
}
return whole;
},
);
return text;
});
// unescape brackets inside of <input type="text" value="...">
text = text.replace(/(<input)([^>]*)(>)/gis, (whole: string, m1: string, m2: string, m3: string) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/__ml\[ml__/g, '[') + m3;
}
return whole;
});
return text;
};
const siteTitle = (lang: MultiLang) => {
return mlang(Config.SITE_TITLE, lang);
return mlang(Config.SITE_TITLE, lang);
};
const siteSlogan = (lang: MultiLang) => {
return mlang(Config.SITE_SLOGAN, lang);
return mlang(Config.SITE_SLOGAN, lang);
};
const Functions = {
escape,
unescape,
htmlspecialchars,
camelCase,
snakeCase,
pascalCase,
base64Encode,
base64Decode,
ordinal,
mlang,
siteTitle,
siteSlogan,
escapeUrl,
unescapeUrl,
htmlspecialchars,
camelCase,
snakeCase,
pascalCase,
base64Encode,
base64Decode,
ordinal,
mlang,
siteTitle,
siteSlogan,
};
export default Functions;
View File
-15
View File
@@ -1,15 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import 'sanitize.css';
import 'sanitize.css/forms.css';
import 'sanitize.css/typography.css';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
+17
View File
@@ -0,0 +1,17 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import 'sanitize.css';
import 'sanitize.css/forms.css';
import 'sanitize.css/typography.css';
import App from './App';
const container = document.getElementById('root');
if (container === null) {
throw new Error('Root container #root was not found in the document.');
}
createRoot(container).render(
<StrictMode>
<App />
</StrictMode>
);
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
+37 -26
View File
@@ -1,70 +1,81 @@
.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;
}
/* sanitize.css forms.css leaves form controls transparent; keep them on white. */
.database :global(.formButton),
.database :global(.fieldInputText),
.database :global(.fieldSelect),
.database :global(.fieldDate select),
.database :global(.fieldDate input),
.database :global(.listTable select) {
background-color: #ffffff;
color: #000000;
}
+65 -78
View File
@@ -1,10 +1,11 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { Route, Routes, useLocation } from 'react-router-dom';
import { Helmet } from '@dr.pogodin/react-helmet';
import type React from 'react';
import { Route, Routes, useLocation } from 'react-router';
import PageNotFound from '../common/lib/PageNotFound';
import { BrainAtlasType, MultiLang } from '../config';
import type { BrainAtlasType, MultiLang } from '../config';
import Functions from '../functions';
import { INDEX_ID_PUBLIC } from './lib/IndexUtil';
import styles from './Xoonips.module.css';
import XoonipsAdvancedSearch from './XoonipsAdvancedSearch';
import XoonipsDetailItem from './XoonipsDetailItem';
import XoonipsSearchByAdvancedKeyword from './XoonipsSearchByAdvancedKeyword';
@@ -12,94 +13,80 @@ import XoonipsSearchByIndexId from './XoonipsSearchByIndexId';
import XoonipsSearchByItemType from './XoonipsSearchByItemType';
import XoonipsSearchByKeyword from './XoonipsSearchByKeyword';
import XoonipsTop from './XoonipsTop';
import { INDEX_ID_PUBLIC } from './lib/IndexUtil';
import styles from './Xoonips.module.css';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const ItemDetail: React.FC<Props> = (props) => {
const { lang, type } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const itemId_ = params.get('item_id') ?? '';
const itemId = /^\d+$/.test(itemId_) ? parseInt(itemId_, 10) : 0;
const doi = params.get('id') ?? '';
return <XoonipsDetailItem lang={lang} id={itemId} doi={doi} type={type} />;
const { lang, type } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const itemId_ = params.get('item_id') ?? '';
const itemId = /^\d+$/.test(itemId_) ? parseInt(itemId_, 10) : 0;
const doi = params.get('id') ?? '';
return <XoonipsDetailItem lang={lang} id={itemId} doi={doi} type={type} />;
};
const ItemList: React.FC<Props> = (props) => {
const { lang, type } = 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} type={type} indexId={indexId} />;
const { lang, type } = 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} type={type} indexId={indexId} />;
};
const ItemSelect: React.FC<Props> = (props) => {
const { lang, type } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const op = params.get('op') ?? '';
switch (op) {
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} subItemType="" type={type} />;
const { lang, type } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const op = params.get('op') ?? '';
switch (op) {
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} subItemType="" type={type} />;
}
case 'itemsubtypesearch': {
const searchItemtype = params.get('search_itemtype') ?? '';
const match = searchItemtype.match(/^xnp([a-z]+)$/);
const itemType = match !== null ? match[1] : '';
const subItemtype = params.get('search_subitemtype') ?? '';
return <XoonipsSearchByItemType lang={lang} itemType={itemType} subItemType={subItemtype} type={type} />;
}
case 'quicksearch': {
return <XoonipsSearchByKeyword lang={lang} type={type} />;
}
case 'advanced': {
return <XoonipsSearchByAdvancedKeyword lang={lang} type={type} />;
}
}
case 'itemsubtypesearch': {
const searchItemtype = params.get('search_itemtype') ?? '';
const match = searchItemtype.match(/^xnp([a-z]+)$/);
const itemType = match !== null ? match[1] : '';
const subItemtype = params.get('search_subitemtype') ?? '';
return (
<XoonipsSearchByItemType
lang={lang}
itemType={itemType}
subItemType={subItemtype}
type={type}
/>
);
}
case 'quicksearch': {
return <XoonipsSearchByKeyword lang={lang} type={type} />;
}
case 'advanced': {
return <XoonipsSearchByAdvancedKeyword lang={lang} type={type} />;
}
}
return <PageNotFound lang={lang} />;
return <PageNotFound lang={lang} />;
};
const Xoonips: React.FC<Props> = (props) => {
const { lang, type } = 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} type={type} />} />
<Route path="index.php" element={<XoonipsTop lang={lang} type={type} />} />
<Route path="detail.php" element={<ItemDetail lang={lang} type={type} />} />
<Route path="listitem.php" element={<ItemList lang={lang} type={type} />} />
<Route path="itemselect.php" element={<ItemSelect lang={lang} type={type} />} />
<Route
path="advanced_search.php"
element={<XoonipsAdvancedSearch lang={lang} type={type} />}
/>
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
const { lang, type } = 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} type={type} />} />
<Route path="index.php" element={<XoonipsTop lang={lang} type={type} />} />
<Route path="detail.php" element={<ItemDetail lang={lang} type={type} />} />
<Route path="listitem.php" element={<ItemList lang={lang} type={type} />} />
<Route path="itemselect.php" element={<ItemSelect lang={lang} type={type} />} />
<Route path="advanced_search.php" element={<XoonipsAdvancedSearch lang={lang} type={type} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
};
export default Xoonips;
+32 -32
View File
@@ -1,47 +1,47 @@
import React from 'react';
import type React from 'react';
import { useNavigate } from 'react-router-dom';
import Config, { BrainAtlasType, MultiLang } from '../config';
import { useNavigate } from 'react-router';
import Config, { type BrainAtlasType, 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;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const XoonipsAdvancedSearch: React.FC<Props> = (props) => {
const { lang, type } = props;
const query = new AdvancedSearchQuery();
const navigate = useNavigate();
const { lang, type } = props;
const query = new AdvancedSearchQuery();
const navigate = useNavigate();
const handleClickSearchButton = () => {
if (!query.empty()) {
const url = ItemUtil.getSearchByAdvancedKeywordsUrl(type, query);
navigate(url);
}
};
const handleClickSearchButton = () => {
if (!query.empty()) {
const url = ItemUtil.getSearchByAdvancedKeywordsUrl(type, 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 type="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 type="button" className="formButton" onClick={handleClickSearchButton}>
Search
</button>
</div>
</div>
);
};
export default XoonipsAdvancedSearch;
+44 -46
View File
@@ -1,63 +1,61 @@
import { Helmet } from '@dr.pogodin/react-helmet';
import React from 'react';
import { Helmet } from 'react-helmet-async';
import Loading from '../common/lib/Loading';
import PageNotFound from '../common/lib/PageNotFound';
import { BrainAtlasType, MultiLang } from '../config';
import type { BrainAtlasType, MultiLang } from '../config';
import Functions from '../functions';
import ItemType from './item-type';
import ItemUtil, { Item } from './lib/ItemUtil';
import ItemUtil, { type Item } from './lib/ItemUtil';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
id: number;
doi: string;
lang: MultiLang;
type: BrainAtlasType;
id: number;
doi: string;
}
const XoonipsDetailItem: React.FC<Props> = (props) => {
const { lang, type, id, doi } = props;
const { lang, type, id, doi } = props;
const [loading, setLoading] = React.useState<boolean>(true);
const [item, setItem] = React.useState<Item | null>(null);
const [loading, setLoading] = React.useState<boolean>(true);
const [item, setItem] = React.useState<Item | null>(null);
React.useEffect(() => {
if (doi !== '') {
ItemUtil.getByDoi(type, doi, (item) => {
setItem(item);
setLoading(false);
});
} else if (id !== 0) {
ItemUtil.get(type, id, (item) => {
setItem(item);
setLoading(false);
});
} else {
setLoading(true);
React.useEffect(() => {
if (doi !== '') {
ItemUtil.getByDoi(type, doi, (item) => {
setItem(item);
setLoading(false);
});
} else if (id !== 0) {
ItemUtil.get(type, id, (item) => {
setItem(item);
setLoading(false);
});
} else {
setLoading(true);
}
}, [type, id, doi]);
if (loading) {
return <Loading />;
}
if (item === null) {
return <PageNotFound lang={lang} />;
}
}, [type, 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} type={type} />
</>
);
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} type={type} />
</>
);
};
export default XoonipsDetailItem;
+19 -19
View File
@@ -1,32 +1,32 @@
import React from 'react';
import type React from 'react';
import { useLocation } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../config';
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
import { useLocation } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../config';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const XoonipsSearchByAdvancedKeyword: React.FC<Props> = (props) => {
const { lang, type } = props;
const location = useLocation();
const query = ItemUtil.getAdvancedSearchQueryByQuery(location.search);
const { lang, type } = props;
const location = useLocation();
const query = ItemUtil.getAdvancedSearchQueryByQuery(location.search);
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
ItemUtil.getListByAdvancedSearchQuery(type, query, condition, func);
};
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
ItemUtil.getListByAdvancedSearchQuery(type, query, condition, func);
};
const baseUrl = ItemUtil.getSearchByAdvancedKeywordsUrl(type, query);
const baseUrl = ItemUtil.getSearchByAdvancedKeywordsUrl(type, query);
return (
<div className="list">
<h3>Listing item</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
return (
<div className="list">
<h3>Listing item</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
};
export default XoonipsSearchByAdvancedKeyword;
+71 -73
View File
@@ -1,92 +1,90 @@
import { Helmet } from '@dr.pogodin/react-helmet';
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import Loading from '../common/lib/Loading';
import PageNotFound from '../common/lib/PageNotFound';
import { BrainAtlasType, MultiLang } from '../config';
import type { BrainAtlasType, MultiLang } from '../config';
import Functions from '../functions';
import IndexUtil, { Index } from './lib/IndexUtil';
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
import IndexUtil, { 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;
type: BrainAtlasType;
indexId: number;
lang: MultiLang;
type: BrainAtlasType;
indexId: number;
}
const XoonipsSearchByIndexId: React.FC<Props> = (props) => {
const { lang, type, indexId } = props;
const { lang, type, indexId } = props;
const [notFound, setNotFound] = React.useState<boolean>(false);
const [index, setIndex] = React.useState<Index | null>(null);
const [parents, setParents] = React.useState<{ title: string; node: React.ReactNode }>({
title: '',
node: null,
});
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (indexId === 0) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByIndexId(type, indexId, condition, func);
}
};
React.useEffect(() => {
IndexUtil.get(type, indexId, (index) => {
setIndex(index);
if (index == null) {
setNotFound(true);
} else {
IndexUtil.getParents(type, indexId, (pIndexes) => {
const parents = pIndexes.map((index: Index) => {
const url: string = IndexUtil.getUrl(type, index.id);
const title = Functions.mlang(index.title, lang);
return (
<React.Fragment key={index.id}>
/ <Link to={url}>{title}</Link>{' '}
</React.Fragment>
);
});
const title = pIndexes
.map((index) => {
return '/' + Functions.mlang(index.title, lang);
})
.join('');
setParents({ title: title, node: parents });
});
}
const [notFound, setNotFound] = React.useState<boolean>(false);
const [index, setIndex] = React.useState<Index | null>(null);
const [parents, setParents] = React.useState<{ title: string; node: React.ReactNode }>({
title: '',
node: null,
});
}, [indexId, lang, type]);
if (notFound) {
return <PageNotFound lang={lang} />;
}
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (indexId === 0) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByIndexId(type, indexId, condition, func);
}
};
if (index == null) {
return <Loading />;
}
const baseUrl = indexId === 0 ? '/' : IndexUtil.getUrl(type, indexId);
React.useEffect(() => {
IndexUtil.get(type, indexId, (index) => {
setIndex(index);
if (index == null) {
setNotFound(true);
} else {
IndexUtil.getParents(type, indexId, (pIndexes) => {
const parents = pIndexes.map((index: Index) => {
const url: string = IndexUtil.getUrl(type, index.id);
const title = Functions.mlang(index.title, lang);
return (
<React.Fragment key={index.id}>
/ <Link to={url}>{title}</Link>{' '}
</React.Fragment>
);
});
const title = pIndexes
.map((index) => {
return `/${Functions.mlang(index.title, lang)}`;
})
.join('');
setParents({ title: title, node: parents });
});
}
});
}, [indexId, lang, type]);
return (
<div className="list">
<Helmet>
<title>
{Functions.mlang(parents.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.node}</div>
<XoonipsListIndex lang={lang} index={index} type={type} />
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
if (notFound) {
return <PageNotFound lang={lang} />;
}
if (index == null) {
return <Loading />;
}
const baseUrl = indexId === 0 ? '/' : IndexUtil.getUrl(type, indexId);
return (
<div className="list">
<Helmet>
<title>
{Functions.mlang(parents.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.node}</div>
<XoonipsListIndex lang={lang} index={index} type={type} />
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
};
export default XoonipsSearchByIndexId;
+23 -23
View File
@@ -1,37 +1,37 @@
import React from 'react';
import type React from 'react';
import { BrainAtlasType, MultiLang } from '../config';
import type { BrainAtlasType, MultiLang } from '../config';
import Functions from '../functions';
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
itemType: string;
subItemType: string;
type: BrainAtlasType;
lang: MultiLang;
itemType: string;
subItemType: string;
type: BrainAtlasType;
}
const XoonipsSearchByItemType: React.FC<Props> = (props) => {
const { lang, itemType, subItemType, type } = props;
const { lang, itemType, subItemType, type } = props;
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (itemType === '') {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByItemType(type, itemType, subItemType, condition, func);
}
};
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (itemType === '') {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByItemType(type, itemType, subItemType, condition, func);
}
};
const baseUrl = ItemUtil.getItemTypeSearchUrl(type, itemType, subItemType);
const baseUrl = ItemUtil.getItemTypeSearchUrl(type, itemType, subItemType);
return (
<div className="list">
<h3>{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
return (
<div className="list">
<h3>{Functions.mlang('[en]Listing item[/en][ja]アイテム一覧[/ja]', lang)}</h3>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
};
export default XoonipsSearchByItemType;
+27 -27
View File
@@ -1,41 +1,41 @@
import React from 'react';
import type React from 'react';
import { useLocation } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../config';
import { useLocation } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../config';
import Functions from '../functions';
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
import ItemUtil, { type SearchCallbackFunc, type SortCondition } from './lib/ItemUtil';
import XoonipsListItem from './lib/XoonipsListItem';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const XoonipsSearchByKeyword: React.FC<Props> = (props) => {
const { lang, type } = props;
const { lang, type } = props;
const location = useLocation();
const query = ItemUtil.getSearchKeywordByQuery(location.search);
const location = useLocation();
const query = ItemUtil.getSearchKeywordByQuery(location.search);
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (query.keyword === '') {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByKeyword(type, query.type, query.keyword, condition, func);
}
};
const searchFunc = (condition: SortCondition, func: SearchCallbackFunc) => {
if (query.keyword === '') {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByKeyword(type, query.type, query.keyword, condition, func);
}
};
const baseUrl = ItemUtil.getSearchByKeywordUrl(type, query.type, query.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)} : {query.keyword}
</p>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</div>
);
const baseUrl = ItemUtil.getSearchByKeywordUrl(type, query.type, query.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)} : {query.keyword}
</p>
<XoonipsListItem lang={lang} url={baseUrl} search={searchFunc} type={type} />
</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;
}
+33 -33
View File
@@ -1,45 +1,45 @@
import React from 'react';
import type React from 'react';
import Config, { BrainAtlasType, MultiLang } from '../config';
import Config, { type BrainAtlasType, type MultiLang } from '../config';
import ItemType from './item-type';
import styles from './XoonipsTop.module.css';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const XoonipsTop: React.FC<Props> = (props) => {
const { lang, type } = 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((itemType, idx) => {
return (
<td key={idx} className={styles.itemType}>
{itemType !== '' && (
<ItemType.Top lang={lang} itemType={'xnp' + itemType} type={type} />
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
const { lang, type } = 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={value.join('-')}>
{value.map((itemType, _idx) => {
return (
<td key={itemType} className={styles.itemType}>
{itemType !== '' && (
<ItemType.Top lang={lang} itemType={`xnp${itemType}`} type={type} />
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
};
export default XoonipsTop;
+131 -139
View File
@@ -1,155 +1,147 @@
import React from 'react';
import type React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { Navigate, useLocation } from 'react-router';
import PageNotFound from '../common/lib/PageNotFound';
import { MultiLang } from '../config';
import type { MultiLang } from '../config';
import Functions from '../functions';
interface Props {
lang: MultiLang;
lang: MultiLang;
}
const XoonipsXoopsPathRedirect: React.FC<Props> = (props) => {
const { lang } = props;
const location = useLocation();
const { lang } = props;
const location = useLocation();
const getRedirectUrl = (): string => {
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 '/database';
}
case 'detail.php': {
const id = query.get('id');
if (id !== null) {
return '/database/item/id/' + Functions.escape(id);
const getRedirectUrl = (): string => {
const pathname = location.pathname || '';
const query = new URLSearchParams(location.search);
const search = /^\/modules\/xoonips(?:\/+(.*))?$/;
const matches = pathname.match(search);
if (matches === null) {
return '';
}
const itemId = query.get('item_id');
if (itemId?.match(/^\d+$/) != null) {
return '/database/item/' + Functions.escape(itemId);
const path = matches[1] || '';
switch (path) {
case '':
case 'index.php': {
return '/database';
}
case 'detail.php': {
const id = query.get('id');
if (id !== null) {
return `/database/item/id/${Functions.escapeUrl(id)}`;
}
const itemId = query.get('item_id');
if (itemId?.match(/^\d+$/) != null) {
return `/database/item/${Functions.escapeUrl(itemId)}`;
}
return '';
}
case 'listitem.php': {
const indexId = query.get('index_id');
if (indexId?.match(/^\d+$/) != null) {
const params = new URLSearchParams();
[
{ qKey: 'orderby', pKey: 'orderby', isNumber: false },
{ qKey: 'order_dir', pKey: 'order_dir', isNumber: true },
{ qKey: 'itemcount', pKey: 'itemcount', isNumber: true },
{ qKey: 'page', pKey: 'page', isNumber: true },
].forEach(({ qKey, pKey, isNumber }) => {
const v = query.get(qKey);
if (v == null || v.length === 0) {
return;
}
if (isNumber && v.match(/^\d+$/) != null) {
return;
}
params.set(pKey, v);
});
const paramStr = params.toString();
return `/database/list/${Functions.escapeUrl(indexId)}${paramStr.length > 0 ? `?${paramStr}` : ''}`;
}
break;
}
case 'itemselect.php': {
const op = query.get('op');
if (op === null) {
break;
}
switch (op) {
case 'quicksearch': {
const keyword = query.get('keyword');
const itemType = query.get('search_itemtype');
if (keyword === null || itemType === null || keyword === '') {
return '';
}
const type = itemType.replace('xnp', '');
if (itemType !== 'basic' && itemType !== 'all' && itemType.match(/^xnp.+/) === null) {
return '';
}
const params = new URLSearchParams({ type, keyword });
[
{ qKey: 'orderby', pKey: 'orderby', isNumber: false },
{ qKey: 'orderdir', pKey: 'order_dir', isNumber: true },
{ qKey: 'item_per_page', pKey: 'itemcount', isNumber: true },
{ qKey: 'page', pKey: 'page', isNumber: true },
].forEach(({ qKey, pKey, isNumber }) => {
const v = query.get(qKey);
if (v == null || v.length === 0) {
return;
}
if (isNumber && v.match(/^\d+$/) != null) {
return;
}
params.set(pKey, v);
});
return `/database/search?${params.toString()}`;
}
case 'itemtypesearch': {
const itemType = query.get('search_itemtype');
if (itemType?.match(/^xnp.+/) == null) {
return '';
}
const type = itemType.replace('xnp', '');
return `/database/search/itemtype/${Functions.escapeUrl(type)}`;
}
case 'itemsubtypesearch': {
let type = '';
let subtype = '';
query.forEach((v, k) => {
if (k.match(/^xnp[a-z]+$/) !== null && v) {
type = k.replace('xnp', '');
return;
}
});
if (type === '') {
return '';
}
query.forEach((v, k) => {
if (k.match(`^xnp${type}_.+$`) !== null && v) {
subtype = v;
return;
}
});
if (subtype === '') {
return '';
}
return `/database/search/itemtype/${Functions.escapeUrl(type)}/${Functions.escapeUrl(subtype)}`;
}
}
return '';
}
case 'advanced_search.php': {
return '/database/advanced';
}
}
return '';
}
case 'listitem.php': {
const indexId = query.get('index_id');
if (indexId?.match(/^\d+$/) != null) {
const params = new URLSearchParams();
[
{ qKey: 'orderby', pKey: 'orderby', isNumber: false },
{ qKey: 'order_dir', pKey: 'order_dir', isNumber: true },
{ qKey: 'itemcount', pKey: 'itemcount', isNumber: true },
{ qKey: 'page', pKey: 'page', isNumber: true },
].forEach(({ qKey, pKey, isNumber }) => {
const v = query.get(qKey);
if (v == null || v.length === 0) {
return;
}
if (isNumber && v.match(/^\d+$/) != null) {
return;
}
params.set(pKey, v);
});
const paramStr = params.toString();
return (
`/database/list/${Functions.escape(indexId)}` +
(paramStr.length > 0 ? `?${paramStr}` : '')
);
}
break;
}
case 'itemselect.php': {
const op = query.get('op');
if (op === null) {
break;
}
switch (op) {
case 'quicksearch': {
const keyword = query.get('keyword');
const itemType = query.get('search_itemtype');
if (keyword === null || itemType === null || keyword === '') {
return '';
}
const type = itemType.replace('xnp', '');
if (itemType !== 'basic' && itemType !== 'all' && itemType.match(/^xnp.+/) === null) {
return '';
}
const params = new URLSearchParams({ type, keyword });
[
{ qKey: 'orderby', pKey: 'orderby', isNumber: false },
{ qKey: 'orderdir', pKey: 'order_dir', isNumber: true },
{ qKey: 'item_per_page', pKey: 'itemcount', isNumber: true },
{ qKey: 'page', pKey: 'page', isNumber: true },
].forEach(({ qKey, pKey, isNumber }) => {
const v = query.get(qKey);
if (v == null || v.length === 0) {
return;
}
if (isNumber && v.match(/^\d+$/) != null) {
return;
}
params.set(pKey, v);
});
return '/database/search?' + params.toString();
}
case 'itemtypesearch': {
const itemType = query.get('search_itemtype');
if (itemType?.match(/^xnp.+/) == null) {
return '';
}
const type = itemType.replace('xnp', '');
return '/database/search/itemtype/' + Functions.escape(type);
}
case 'itemsubtypesearch': {
let type = '';
let subtype = '';
query.forEach((v, k) => {
if (k.match(/^xnp[a-z]+$/) !== null && !!v) {
type = k.replace('xnp', '');
return;
}
});
if (type === '') {
return '';
}
query.forEach((v, k) => {
if (k.match(`^xnp${type}_.+$`) !== null && !!v) {
subtype = v;
return;
}
});
if (subtype === '') {
return '';
}
return (
'/database/search/itemtype/' +
Functions.escape(type) +
'/' +
Functions.escape(subtype)
);
}
}
return '';
}
case 'advanced_search.php': {
return '/database/advanced';
}
}
return '';
};
};
const url = getRedirectUrl();
if (url === '') {
return <PageNotFound lang={lang} />;
}
return <Navigate to={url} />;
const url = getRedirectUrl();
if (url === '') {
return <PageNotFound lang={lang} />;
}
return <Navigate to={url} />;
};
export default XoonipsXoopsPathRedirect;
+69 -71
View File
@@ -1,109 +1,107 @@
.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;
/* sanitize.css forms.css leaves buttons transparent */
background-color: #ffffff;
color: #000000;
}
.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) no-repeat -18px 0;
}
.indexTree:global(
.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:not(:last-child)
) {
background-position: -9px 0;
/* treeData has a single root, so the first indent unit never carries a line. */
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent .rc-tree-indent-unit:first-child) {
display: none;
}
.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(.rc-tree-indent-unit-end)) {
background-position: -9px 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;
/* The node's own branch (├ or └) is drawn after the indent units. */
.indexTree:global(.rc-tree .rc-tree-treenode .rc-tree-indent)::after {
content: "";
display: inline-block;
vertical-align: bottom;
height: 20px;
width: 9px;
background: url(../assets/images/tree_line.png) no-repeat 0 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode.rc-tree-treenode-leaf-last .rc-tree-indent)::after {
background-position: -27px 0;
}
.indexTree:global(.rc-tree .rc-tree-treenode:first-child .rc-tree-indent)::after {
display: none;
}
.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) no-repeat;
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;
.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher-noop) {
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;
.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher_open) {
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;
.indexTree:global(.rc-tree .rc-tree-treenode:not(:first-child) .rc-tree-switcher.rc-tree-switcher_close) {
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;
}
+107 -110
View File
@@ -1,134 +1,131 @@
import React from 'react';
import Tree from 'rc-tree';
import { DataNode, EventDataNode } from 'rc-tree/lib/interface';
import type { DataNode, EventDataNode } from 'rc-tree/lib/interface';
import React from 'react';
import { useNavigate } from 'react-router';
import Loading from '../../common/lib/Loading';
import { BrainAtlasType, MultiLang } from '../../config';
import type { BrainAtlasType, MultiLang } from '../../config';
import Functions from '../../functions';
import IndexUtil, { INDEX_ID_PUBLIC, Index } from '../lib/IndexUtil';
import IndexUtil, { INDEX_ID_PUBLIC, type Index } from '../lib/IndexUtil';
import styles from './IndexTree.module.css';
interface Props {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
const IndexTree: React.FC<Props> = (props) => {
const { lang, type } = props;
const navigate = useNavigate();
const { lang, type } = props;
const navigate = useNavigate();
const [tree, setTree] = React.useState<DataNode[]>([]);
const [keys, setKeys] = React.useState<string[]>([]);
const [expandedKeys, setExpandedKeys] = React.useState<string[]>([]);
const [selectedKeys, setSelectedKeys] = React.useState<number[]>([]);
const [tree, setTree] = React.useState<DataNode[]>([]);
const [keys, setKeys] = React.useState<string[]>([]);
const [expandedKeys, setExpandedKeys] = React.useState<string[]>([]);
const [selectedKeys, setSelectedKeys] = React.useState<number[]>([]);
React.useEffect(() => {
const tree: DataNode[] = [];
const keys: string[] = [];
const eKeys: string[] = [];
const makeTreeNode = (index: Index, depth: number, func: (node: DataNode) => void): void => {
const title =
Functions.mlang(index.title, lang) +
(index.numOfItems > 0 ? ' (' + index.numOfItems + ')' : '');
IndexUtil.getChildren(type, index.id, (children) => {
if (children.length === 0) {
func({ key: String(index.id), title: title });
} else {
if (depth < 1) {
eKeys.push(String(index.id));
}
keys.push(String(index.id));
const childTreeNodes: DataNode[] = [];
let called = 0;
children.forEach((value: Index) => {
makeTreeNode(value, depth + 1, (cNode) => {
called++;
childTreeNodes.push(cNode);
if (called === children.length) {
func({ key: String(index.id), title: title, children: childTreeNodes });
}
React.useEffect(() => {
const tree: DataNode[] = [];
const keys: string[] = [];
const eKeys: string[] = [];
const makeTreeNode = (index: Index, depth: number, func: (node: DataNode) => void): void => {
const title = Functions.mlang(index.title, lang) + (index.numOfItems > 0 ? ` (${index.numOfItems})` : '');
IndexUtil.getChildren(type, index.id, (children) => {
if (children.length === 0) {
func({ key: String(index.id), title: title });
} else {
if (depth < 1) {
eKeys.push(String(index.id));
}
keys.push(String(index.id));
const childTreeNodes: DataNode[] = [];
let called = 0;
children.forEach((value: Index) => {
makeTreeNode(value, depth + 1, (cNode) => {
called++;
childTreeNodes.push(cNode);
if (called === children.length) {
func({ key: String(index.id), title: title, children: childTreeNodes });
}
});
});
}
});
});
}
});
};
IndexUtil.get(type, INDEX_ID_PUBLIC, (index) => {
if (index != null) {
makeTreeNode(index, 0, (node) => {
tree.push(node);
setTree(tree);
setKeys(keys);
setExpandedKeys(eKeys);
setSelectedKeys([]);
};
IndexUtil.get(type, INDEX_ID_PUBLIC, (index) => {
if (index != null) {
makeTreeNode(index, 0, (node) => {
tree.push(node);
setTree(tree);
setKeys(keys);
setExpandedKeys(eKeys);
setSelectedKeys([]);
});
}
});
}
});
}, [lang, type]);
}, [lang, type]);
const handleClickOpenAll = () => {
setExpandedKeys(keys);
};
const handleClickOpenAll = () => {
setExpandedKeys(keys);
};
const handleClickCloseAll = () => {
setExpandedKeys([]);
};
const handleClickCloseAll = () => {
setExpandedKeys([]);
};
const handleExpand: (
expandedKeys: React.Key[],
info: {
node: EventDataNode<DataNode>;
expanded: boolean;
nativeEvent: MouseEvent;
},
) => void = (expandedKeys) => {
const keys: string[] = expandedKeys.map((key) => {
return typeof key === 'string' ? key : String(key);
});
setExpandedKeys(keys);
};
const handleExpand: (
expandedKeys: React.Key[],
info: {
node: EventDataNode<DataNode>;
expanded: boolean;
nativeEvent: MouseEvent;
}
) => void = (expandedKeys) => {
const keys: string[] = expandedKeys.map((key) => {
return typeof key === 'string' ? key : String(key);
});
setExpandedKeys(keys);
};
const handleSelect: (
selectedKeys: React.Key[],
info: {
event: 'select';
selected: boolean;
node: EventDataNode<DataNode>;
selectedNodes: DataNode[];
nativeEvent: MouseEvent;
},
) => void = (selectedKeys) => {
const selectedKey = selectedKeys.shift() ?? 0;
const key = typeof selectedKey === 'string' ? parseInt(selectedKey, 10) : selectedKey;
const url = IndexUtil.getUrl(type, key);
navigate(url);
setSelectedKeys([]);
};
const handleSelect: (
selectedKeys: React.Key[],
info: {
event: 'select';
selected: boolean;
node: EventDataNode<DataNode>;
selectedNodes: DataNode[];
nativeEvent: MouseEvent;
}
) => void = (selectedKeys) => {
const selectedKey = selectedKeys.shift() ?? 0;
const key = typeof selectedKey === 'string' ? parseInt(selectedKey, 10) : Number(selectedKey);
const url = IndexUtil.getUrl(type, key);
navigate(url);
setSelectedKeys([]);
};
if (tree.length === 0) {
return <Loading />;
}
if (tree.length === 0) {
return <Loading />;
}
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 type="button" className={styles.formButton} onClick={handleClickOpenAll}>
open all
</button>
<button type="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;
@@ -1,31 +1,31 @@
import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
import AdvancedSearchBase, { type AdvancedSearchBaseProps } 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 = '';
}
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 = '';
}
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;
}
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;
}
}
export default BinderAdvancedSearch;
+88 -89
View File
@@ -1,108 +1,107 @@
import React from 'react';
import ItemType from '..';
import { BrainAtlasType, MultiLang } from '../../../config';
import type { BrainAtlasType, MultiLang } from '../../../config';
import Functions from '../../../functions';
import ItemUtil, { Item, ItemBinder } from '../../lib/ItemUtil';
import ItemUtil, { type Item, type ItemBinder } from '../../lib/ItemUtil';
import ItemType from '..';
import DetailBase from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
interface Props {
lang: MultiLang;
item: ItemBinder;
type: BrainAtlasType;
lang: MultiLang;
item: ItemBinder;
type: BrainAtlasType;
}
const BinderLinkItems: React.FC<Props> = (props) => {
const { lang, item, type } = props;
const [items, setItems] = React.useState<Item[]>([]);
const { lang, item, type } = props;
const [items, setItems] = React.useState<Item[]>([]);
const isMounted = React.useRef<boolean>(false);
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
const isMounted = React.useRef<boolean>(false);
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
React.useEffect(() => {
const itemIds = item.item_link;
ItemUtil.getList(type, itemIds, (results) => {
if (isMounted.current) {
setItems(results.data);
}
});
}, [item.item_link, type]);
React.useEffect(() => {
const itemIds = item.item_link;
ItemUtil.getList(type, itemIds, (results) => {
if (isMounted.current) {
setItems(results.data);
}
});
}, [item.item_link, type]);
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} type={type} />
</td>
</tr>
);
})}
</tbody>
</table>
);
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} type={type} />
</td>
</tr>
);
})}
</tbody>
</table>
);
};
class BinderDetail extends DetailBase {
getFields() {
const { lang, type } = 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.Contributor 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} type={type} />,
},
];
}
getFields() {
const { lang, type } = 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.Contributor 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} type={type} />,
},
];
}
render() {
const { lang, type } = 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} type={type} />
</>
);
}
render() {
const { lang, type } = 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} type={type} />
</>
);
}
}
export default BinderDetail;
+19 -20
View File
@@ -1,28 +1,27 @@
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import Functions from '../../../functions';
import { ItemBinder } from '../../lib/ItemUtil';
import ListBase, { ListBaseProps } from '../lib/ListBase';
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;
}
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)}
</>
);
}
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)}
</>
);
}
}
export default BinderList;
+8 -9
View File
@@ -1,15 +1,14 @@
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]';
}
constructor(props: TopBaseProps) {
super(props);
this.type = 'binder';
this.label = 'Binder';
this.icon = iconFile;
this.description = '[en]Binder collection.[/en][ja]バインダー[/ja]';
}
}
export default BinderTop;
+4 -4
View File
@@ -4,10 +4,10 @@ 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,55 +1,55 @@
import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
import AdvancedSearchBase, { type AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
class BookAdvancedSearch extends AdvancedSearchBase {
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = 'book';
this.title = 'Book';
this.state.values.title = '';
this.state.values.keyword = '';
this.state.values.description = '';
this.state.values.doi = '';
this.state.values.author = '';
this.state.values.editor = '';
this.state.values.publisher = '';
this.state.values.publication_year = '';
this.state.values.isbn = '';
this.state.values['file.book_pdf.original_file_name'] = '';
}
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = 'book';
this.title = 'Book';
this.state.values.title = '';
this.state.values.keyword = '';
this.state.values.description = '';
this.state.values.doi = '';
this.state.values.author = '';
this.state.values.editor = '';
this.state.values.publisher = '';
this.state.values.publication_year = '';
this.state.values.isbn = '';
this.state.values['file.book_pdf.original_file_name'] = '';
}
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;
}
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;
}
}
export default BookAdvancedSearch;
+73 -73
View File
@@ -1,81 +1,81 @@
import Functions from '../../../functions';
import { ItemBook } from '../../lib/ItemUtil';
import type { ItemBook } from '../../lib/ItemUtil';
import DetailBase from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
class BookDetail extends DetailBase {
getFields() {
const { lang, type } = 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.Contributor 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}
fileType="book_pdf"
downloadLimit={item.attachment_dl_limit}
type={type}
/>
),
},
{
label: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
}
getFields() {
const { lang, type } = 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.Contributor 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}
fileType="book_pdf"
downloadLimit={item.attachment_dl_limit}
type={type}
/>
),
},
{
label: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
}
}
export default BookDetail;
+20 -29
View File
@@ -1,37 +1,28 @@
import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import Functions from '../../../functions';
import { ItemBook } from '../../lib/ItemUtil';
import ListBase, { ListBaseProps } from '../lib/ListBase';
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;
}
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}
</>
);
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemBook;
const authors = item.author.map((author) => Functions.mlang(author, lang)).join(', ');
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
{authors}
</>
);
}
}
export default BookList;
+8 -9
View File
@@ -1,15 +1,14 @@
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]';
}
constructor(props: TopBaseProps) {
super(props);
this.type = 'book';
this.label = 'Book';
this.icon = iconFile;
this.description = '[en]Related book collection.[/en][ja]関連書籍[/ja]';
}
}
export default BookTop;
+4 -4
View File
@@ -4,10 +4,10 @@ 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,70 +1,65 @@
import { ItemConferenceSubTypes } from '../../lib/ItemUtil';
import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
import AdvancedSearchBase, { type AdvancedSearchBaseProps } 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');
}
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');
}
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>
</>
);
}
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>
</>
);
}
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;
}
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;
}
}
export default ConferenceAdvancedSearch;
@@ -1,93 +1,79 @@
import Functions from '../../../functions';
import { ItemConference } from '../../lib/ItemUtil';
import type { ItemConference } from '../../lib/ItemUtil';
import DetailBase from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
import ConferenceUtil from './ConferenceUtil';
class ConferenceDetail extends DetailBase {
getFields() {
const { lang, type } = 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.Contributor 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}
fileType="conference_file"
type={type}
/>
),
},
{
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}
fileType="conference_paper"
type={type}
/>
),
},
{
label: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
}
getFields() {
const { lang, type } = 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.Contributor 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} fileType="conference_file" type={type} />,
},
{
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} fileType="conference_paper" type={type} />,
},
{
label: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
}
}
export default ConferenceDetail;
@@ -1,41 +1,31 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import Functions from '../../../functions';
import { ItemConference } from '../../lib/ItemUtil';
import ListBase, { ListBaseProps } from '../lib/ListBase';
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';
import iconFile from '../../assets/images/icon_conference.gif';
class ConferenceList extends ListBase {
constructor(props: ListBaseProps) {
super(props);
this.label = 'Conference';
this.icon = iconFile;
}
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 (
<React.Fragment key={i}>
{i > 0 && ', '}
{Functions.mlang(author, lang)}
</React.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}
</>
);
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemConference;
const authors = item.author.map((author) => Functions.mlang(author, lang)).join(', ');
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}
</>
);
}
}
export default ConferenceList;
@@ -1,17 +1,16 @@
import { ItemConferenceSubTypes } from '../../lib/ItemUtil';
import TopBase, { TopBaseProps } from '../lib/TopBase';
import iconFile from '../../assets/images/icon_conference.gif';
import { ItemConferenceSubTypes } from '../../lib/ItemUtil';
import TopBase, { type TopBaseProps } from '../lib/TopBase';
class ConferenceTop extends TopBase {
constructor(props: TopBaseProps) {
super(props);
this.type = 'conference';
this.label = 'Conference';
this.icon = iconFile;
this.description = '[en]Electrical presentation files for conference.[/en][ja]学会発表[/ja]';
this.subTypes = ItemConferenceSubTypes;
}
constructor(props: TopBaseProps) {
super(props);
this.type = 'conference';
this.label = 'Conference';
this.icon = iconFile;
this.description = '[en]Electrical presentation files for conference.[/en][ja]学会発表[/ja]';
this.subTypes = ItemConferenceSubTypes;
}
}
export default ConferenceTop;
@@ -1,68 +1,55 @@
import React from 'react';
import { MultiLang } from '../../../config';
import { ItemConference, ItemConferenceSubType, ItemConferenceSubTypes } from '../../lib/ItemUtil';
import type React from 'react';
import type { MultiLang } from '../../../config';
import { type ItemConference, type ItemConferenceSubType, ItemConferenceSubTypes } from '../../lib/ItemUtil';
interface PresentationTypeProps {
lang: MultiLang;
type: ItemConferenceSubType;
lang: MultiLang;
type: ItemConferenceSubType;
}
const PresentationType: React.FC<PresentationTypeProps> = (props: PresentationTypeProps) => {
const { type } = props;
const subtype = ItemConferenceSubTypes.find((value) => {
return value.type === type;
});
if (typeof subtype === 'undefined') {
return null;
}
return <span>{subtype.label}</span>;
const { type } = props;
const subtype = ItemConferenceSubTypes.find((value) => {
return value.type === type;
});
if (typeof subtype === 'undefined') {
return null;
}
return <span>{subtype.label}</span>;
};
interface ConferenceDateProps {
lang: MultiLang;
item: ItemConference;
lang: MultiLang;
item: ItemConference;
}
const ConferenceDate: React.FC<ConferenceDateProps> = (props: ConferenceDateProps) => {
const { item } = props;
const monthStr = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const from =
'From: ' +
monthStr[item.conference_from_month - 1] +
' ' +
item.conference_from_mday +
', ' +
item.conference_from_year;
const to =
'To: ' +
monthStr[item.conference_to_month - 1] +
' ' +
item.conference_to_mday +
', ' +
item.conference_to_year;
return (
<span>
{from} {to}
</span>
);
const { item } = props;
const monthStr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const from =
'From: ' +
monthStr[item.conference_from_month - 1] +
' ' +
item.conference_from_mday +
', ' +
item.conference_from_year;
const to =
'To: ' +
monthStr[item.conference_to_month - 1] +
' ' +
item.conference_to_mday +
', ' +
item.conference_to_year;
return (
<span>
{from} {to}
</span>
);
};
const ConferenceUtil = {
PresentationType,
ConferenceDate,
PresentationType,
ConferenceDate,
};
export default ConferenceUtil;
+4 -4
View File
@@ -4,10 +4,10 @@ import ConferenceList from './ConferenceList';
import ConferenceTop from './ConferenceTop';
const ItemTypeConference = {
Top: ConferenceTop,
List: ConferenceList,
Detail: ConferenceDetail,
AdvancedSearch: ConferenceAdvancedSearch,
Top: ConferenceTop,
List: ConferenceList,
Detail: ConferenceDetail,
AdvancedSearch: ConferenceAdvancedSearch,
};
export default ItemTypeConference;
@@ -1,71 +1,66 @@
import { ItemDataSubTypes } from '../../lib/ItemUtil';
import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
import AdvancedSearchBase, { type AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
class DataAdvancedSearch extends AdvancedSearchBase {
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = 'data';
this.title = 'Data';
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.keyword = '';
this.state.values.description = '';
this.state.values.doi = '';
this.state.values.data_type = '';
this.state.values.experimenter = '';
this.state.values.publication_year = year;
this.state.values.publication_month = month;
this.state.values.publication_mday = mday;
this.state.values['file.preview.caption'] = '';
this.state.values['file.data_file.original_file_name'] = '';
this.setIgnoreKey('publication_year');
this.setIgnoreKey('publication_month');
this.setIgnoreKey('publication_mday');
}
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = 'data';
this.title = 'Data';
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.keyword = '';
this.state.values.description = '';
this.state.values.doi = '';
this.state.values.data_type = '';
this.state.values.experimenter = '';
this.state.values.publication_year = year;
this.state.values.publication_month = month;
this.state.values.publication_mday = mday;
this.state.values['file.preview.caption'] = '';
this.state.values['file.data_file.original_file_name'] = '';
this.setIgnoreKey('publication_year');
this.setIgnoreKey('publication_month');
this.setIgnoreKey('publication_mday');
}
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) },
{
label: '[en]Data Type[/en][ja]データタイプ[/ja]',
value: this.renderFieldSelect('data_type', ItemDataSubTypes),
},
{
label: '[en]Experimenter[/en][ja]実験者[/ja]',
value: this.renderFieldInputText('experimenter', 50),
},
{
label: '[en]Date[/en][ja]日付[/ja]',
value: this.renderFieldDate(
'',
'publication_year',
'publication_month',
'publication_mday',
),
},
{
label: '[en]Caption[/en][ja]キャプション[/ja]',
value: this.renderFieldInputText('file.preview.caption', 50),
},
{
label: '[en]Data File[/en][ja]データファイル[/ja]',
value: this.renderFieldInputText('file.data_file.original_file_name', 50),
},
];
return rows;
}
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) },
{
label: '[en]Data Type[/en][ja]データタイプ[/ja]',
value: this.renderFieldSelect('data_type', ItemDataSubTypes),
},
{
label: '[en]Experimenter[/en][ja]実験者[/ja]',
value: this.renderFieldInputText('experimenter', 50),
},
{
label: '[en]Date[/en][ja]日付[/ja]',
value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday'),
},
{
label: '[en]Caption[/en][ja]キャプション[/ja]',
value: this.renderFieldInputText('file.preview.caption', 50),
},
{
label: '[en]Data File[/en][ja]データファイル[/ja]',
value: this.renderFieldInputText('file.data_file.original_file_name', 50),
},
];
return rows;
}
}
export default DataAdvancedSearch;
+106 -106
View File
@@ -1,117 +1,117 @@
import Functions from '../../../functions';
import ItemUtil, { ItemData } from '../../lib/ItemUtil';
import ItemUtil, { type ItemData } from '../../lib/ItemUtil';
import DetailBase from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
import SimPFLinkIcon from '../lib/field/SimPFLinkIcon';
import DataUtil from './DataUtil';
class DataDetail extends DetailBase {
getFields() {
const { lang, type } = this.props;
const item = this.props.item as ItemData;
const fields = [
{ label: 'ID', value: item.doi },
{
label: '[en]Language[/en][ja]言語[/ja]',
value: <ItemTypeField.Language lang={lang} itemLang={item.lang} />,
},
{ 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]Date[/en][ja]日付[/ja]',
value: (
<ItemTypeField.PublicationDate
lang={lang}
year={item.publication_year}
month={item.publication_month}
mday={item.publication_mday}
/>
),
},
{
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.Contributor 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]Data Type[/en][ja]データタイプ[/ja]',
value: <DataUtil.DataType lang={lang} type={item.data_type} />,
},
{
label: '[en]Experimenter[/en][ja]実験者[/ja]',
value: <ItemTypeField.Author lang={lang} author={item.experimenter} />,
},
{
label: '[en]Preview[/en][ja]プレビュー[/ja]',
value: <ItemTypeField.Preview lang={lang} file={item.file} type={type} />,
},
{
label: '[en]Data File[/en][ja]データファイル[/ja]',
value: (
<ItemTypeField.ItemFile
lang={lang}
file={item.file}
fileType="data_file"
rights={item.rights}
useCc={item.use_cc}
ccCommercialUse={item.cc_commercial_use}
ccModification={item.cc_modification}
downloadLimit={item.attachment_dl_limit}
type={type}
/>
),
},
{ label: 'Readme', value: <ItemTypeField.Readme lang={lang} readme={item.readme} /> },
{
label: 'Rights',
value: (
<ItemTypeField.Rights
lang={lang}
rights={item.rights}
useCc={item.use_cc}
ccCommercialUse={item.cc_commercial_use}
ccModification={item.cc_modification}
/>
),
},
{
label: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id);
if (simpfLinkUrl !== '') {
const field = {
label: 'Online Simulation',
value: <SimPFLinkIcon lang={lang} url={simpfLinkUrl} isDetail={true} />,
};
fields.splice(14, 0, field);
getFields() {
const { lang, type } = this.props;
const item = this.props.item as ItemData;
const fields = [
{ label: 'ID', value: item.doi },
{
label: '[en]Language[/en][ja]言語[/ja]',
value: <ItemTypeField.Language lang={lang} itemLang={item.lang} />,
},
{ 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]Date[/en][ja]日付[/ja]',
value: (
<ItemTypeField.PublicationDate
lang={lang}
year={item.publication_year}
month={item.publication_month}
mday={item.publication_mday}
/>
),
},
{
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.Contributor 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]Data Type[/en][ja]データタイプ[/ja]',
value: <DataUtil.DataType lang={lang} type={item.data_type} />,
},
{
label: '[en]Experimenter[/en][ja]実験者[/ja]',
value: <ItemTypeField.Author lang={lang} author={item.experimenter} />,
},
{
label: '[en]Preview[/en][ja]プレビュー[/ja]',
value: <ItemTypeField.Preview lang={lang} file={item.file} type={type} />,
},
{
label: '[en]Data File[/en][ja]データファイル[/ja]',
value: (
<ItemTypeField.ItemFile
lang={lang}
file={item.file}
fileType="data_file"
rights={item.rights}
useCc={item.use_cc}
ccCommercialUse={item.cc_commercial_use}
ccModification={item.cc_modification}
downloadLimit={item.attachment_dl_limit}
type={type}
/>
),
},
{ label: 'Readme', value: <ItemTypeField.Readme lang={lang} readme={item.readme} /> },
{
label: 'Rights',
value: (
<ItemTypeField.Rights
lang={lang}
rights={item.rights}
useCc={item.use_cc}
ccCommercialUse={item.cc_commercial_use}
ccModification={item.cc_modification}
/>
),
},
{
label: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
const simpfLinkUrl = ItemUtil.getSimPFLinkUrl(item.item_id);
if (simpfLinkUrl !== '') {
const field = {
label: 'Online Simulation',
value: <SimPFLinkIcon lang={lang} url={simpfLinkUrl} isDetail={true} />,
};
fields.splice(14, 0, field);
}
return fields;
}
return fields;
}
}
export default DataDetail;
+20 -30
View File
@@ -1,38 +1,28 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import Functions from '../../../functions';
import { ItemData } from '../../lib/ItemUtil';
import ListBase, { ListBaseProps } from '../lib/ListBase';
import iconFile from '../../assets/images/icon_data.gif';
import type { ItemData } from '../../lib/ItemUtil';
import ListBase, { type ListBaseProps } from '../lib/ListBase';
class DataList extends ListBase {
constructor(props: ListBaseProps) {
super(props);
this.label = 'Data';
this.icon = iconFile;
}
constructor(props: ListBaseProps) {
super(props);
this.label = 'Data';
this.icon = iconFile;
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemData;
const authors = item.experimenter.map((author, i) => {
return (
<React.Fragment key={i}>
{i > 0 && ', '}
{Functions.mlang(author, lang)}
</React.Fragment>
);
});
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
{authors}
</>
);
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemData;
const authors = item.experimenter.map((author) => Functions.mlang(author, lang)).join(', ');
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
{authors}
</>
);
}
}
export default DataList;
+11 -12
View File
@@ -1,18 +1,17 @@
import { ItemDataSubTypes } from '../../lib/ItemUtil';
import TopBase, { TopBaseProps } from '../lib/TopBase';
import iconFile from '../../assets/images/icon_data.gif';
import { ItemDataSubTypes } from '../../lib/ItemUtil';
import TopBase, { type TopBaseProps } from '../lib/TopBase';
class DataTop extends TopBase {
constructor(props: TopBaseProps) {
super(props);
this.type = 'data';
this.label = 'Data';
this.icon = iconFile;
this.description =
'[en]Result data in numerical text/image/movie formats.[/en][ja]実験結果の数値データ/画像/動画など[/ja]';
this.subTypes = ItemDataSubTypes;
}
constructor(props: TopBaseProps) {
super(props);
this.type = 'data';
this.label = 'Data';
this.icon = iconFile;
this.description =
'[en]Result data in numerical text/image/movie formats.[/en][ja]実験結果の数値データ/画像/動画など[/ja]';
this.subTypes = ItemDataSubTypes;
}
}
export default DataTop;
+14 -14
View File
@@ -1,25 +1,25 @@
import React from 'react';
import { MultiLang } from '../../../config';
import { ItemDataSubType, ItemDataSubTypes } from '../../lib/ItemUtil';
import type React from 'react';
import type { MultiLang } from '../../../config';
import { type ItemDataSubType, ItemDataSubTypes } from '../../lib/ItemUtil';
interface DataTypeProps {
lang: MultiLang;
type: ItemDataSubType;
lang: MultiLang;
type: ItemDataSubType;
}
const DataType: React.FC<DataTypeProps> = (props: DataTypeProps) => {
const { type } = props;
const subtype = ItemDataSubTypes.find((value) => {
return value.type === type;
});
if (typeof subtype === 'undefined') {
return null;
}
return <span>{subtype.label}</span>;
const { type } = props;
const subtype = ItemDataSubTypes.find((value) => {
return value.type === type;
});
if (typeof subtype === 'undefined') {
return null;
}
return <span>{subtype.label}</span>;
};
const DataUtil = {
DataType,
DataType,
};
export default DataUtil;
+4 -4
View File
@@ -4,10 +4,10 @@ import DataList from './DataList';
import DataTop from './DataTop';
const ItemTypeData = {
Top: DataTop,
List: DataList,
Detail: DataDetail,
AdvancedSearch: DataAdvancedSearch,
Top: DataTop,
List: DataList,
Detail: DataDetail,
AdvancedSearch: DataAdvancedSearch,
};
export default ItemTypeData;
@@ -1,44 +1,44 @@
import AdvancedSearchBase, { AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
import AdvancedSearchBase, { type AdvancedSearchBaseProps } from '../lib/AdvancedSearchBase';
class FilesAdvancedSearch extends AdvancedSearchBase {
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = 'files';
this.title = 'Files';
this.state.values.title = '';
this.state.values.data_file_name = '';
this.state.values.data_file_mimetype = '';
this.state.values.data_file_filetype = '';
this.state.values.keyword = '';
this.state.values.description = '';
}
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.type = 'files';
this.title = 'Files';
this.state.values.title = '';
this.state.values.data_file_name = '';
this.state.values.data_file_mimetype = '';
this.state.values.data_file_filetype = '';
this.state.values.keyword = '';
this.state.values.description = '';
}
getRows() {
const rows = [
{ label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) },
{
label: '- [en]File Name[/en][ja]ファイル名[/ja]',
value: this.renderFieldInputText('data_file_name', 50),
},
{
label: '- [en]MIME Type[/en][ja]MIMEタイプ[/ja]',
value: this.renderFieldInputText('data_file_mimetype', 50),
},
{
label: '- [en]File Type[/en][ja]ファイルタイプ[/ja]',
value: this.renderFieldInputText('data_file_filetype', 20),
},
{
label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]',
value: this.renderFieldInputText('keyword', 50),
},
{
label: '[en]Description[/en][ja]概要[/ja]',
value: this.renderFieldInputText('description', 50),
},
];
return rows;
}
getRows() {
const rows = [
{ label: '[en]Title[/en][ja]タイトル[/ja]', value: this.renderFieldInputText('title', 50) },
{
label: '- [en]File Name[/en][ja]ファイル名[/ja]',
value: this.renderFieldInputText('data_file_name', 50),
},
{
label: '- [en]MIME Type[/en][ja]MIMEタイプ[/ja]',
value: this.renderFieldInputText('data_file_mimetype', 50),
},
{
label: '- [en]File Type[/en][ja]ファイルタイプ[/ja]',
value: this.renderFieldInputText('data_file_filetype', 20),
},
{
label: '[en]Free Keywords[/en][ja]フリーキーワード[/ja]',
value: this.renderFieldInputText('keyword', 50),
},
{
label: '[en]Description[/en][ja]概要[/ja]',
value: this.renderFieldInputText('description', 50),
},
];
return rows;
}
}
export default FilesAdvancedSearch;
+53 -55
View File
@@ -1,63 +1,61 @@
import Functions from '../../../functions';
import { ItemFiles } from '../../lib/ItemUtil';
import type { ItemFiles } from '../../lib/ItemUtil';
import DetailBase from '../lib/DetailBase';
import ItemTypeField from '../lib/field';
class FilesDetail extends DetailBase {
getFields() {
const { lang, type } = this.props;
const item = this.props.item as ItemFiles;
return [
{ label: 'ID', value: item.doi },
{
label: '[en]Language[/en][ja]言語[/ja]',
value: <ItemTypeField.Language lang={lang} itemLang={item.lang} />,
},
{ label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) },
{
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.Contributor 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]Data File[/en][ja]データファイル[/ja]',
value: (
<ItemTypeField.ItemFile lang={lang} file={item.file} fileType="files_file" type={type} />
),
},
{ label: '- [en]File Name[/en][ja]ファイル名[/ja]', value: item.data_file_name },
{ label: '- [en]MIME Type[/en][ja]MIMEタイプ[/ja]', value: item.data_file_mimetype },
{ label: '- [en]File Type[/en][ja]ファイルタイプ[/ja]', value: item.data_file_filetype },
{
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: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
}
getFields() {
const { lang, type } = this.props;
const item = this.props.item as ItemFiles;
return [
{ label: 'ID', value: item.doi },
{
label: '[en]Language[/en][ja]言語[/ja]',
value: <ItemTypeField.Language lang={lang} itemLang={item.lang} />,
},
{ label: '[en]Title[/en][ja]タイトル[/ja]', value: Functions.mlang(item.title, lang) },
{
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.Contributor 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]Data File[/en][ja]データファイル[/ja]',
value: <ItemTypeField.ItemFile lang={lang} file={item.file} fileType="files_file" type={type} />,
},
{ label: '- [en]File Name[/en][ja]ファイル名[/ja]', value: item.data_file_name },
{ label: '- [en]MIME Type[/en][ja]MIMEタイプ[/ja]', value: item.data_file_mimetype },
{ label: '- [en]File Type[/en][ja]ファイルタイプ[/ja]', value: item.data_file_filetype },
{
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: 'Index',
value: <ItemTypeField.ItemIndex lang={lang} index={item.index} type={type} />,
},
{
label: '[en]Related to[/en][ja]関連アイテム[/ja]',
value: <ItemTypeField.RelatedTo lang={lang} relatedTo={item.related_to} type={type} />,
},
];
}
}
export default FilesDetail;
+22 -23
View File
@@ -1,31 +1,30 @@
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import Functions from '../../../functions';
import { ItemFiles } from '../../lib/ItemUtil';
import ListBase, { ListBaseProps } from '../lib/ListBase';
import Contributor from '../lib/field/Contributor';
import iconFile from '../../assets/images/icon_files.gif';
import type { ItemFiles } from '../../lib/ItemUtil';
import Contributor from '../lib/field/Contributor';
import ListBase, { type ListBaseProps } from '../lib/ListBase';
class FilesList extends ListBase {
constructor(props: ListBaseProps) {
super(props);
this.label = 'Files';
this.icon = iconFile;
}
constructor(props: ListBaseProps) {
super(props);
this.label = 'Files';
this.icon = iconFile;
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemFiles;
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
<Contributor lang={lang} uname={item.uname} name={item.name} />
<br />
{item.data_file_mimetype}
</>
);
}
renderBody() {
const { lang } = this.props;
const item = this.props.item as ItemFiles;
return (
<>
<Link to={this.url}>{Functions.mlang(item.title, lang)}</Link>
<br />
<Contributor lang={lang} uname={item.uname} name={item.name} />
<br />
{item.data_file_mimetype}
</>
);
}
}
export default FilesList;
+10 -11
View File
@@ -1,17 +1,16 @@
import { ItemFilesSubTypes } from '../../lib/ItemUtil';
import TopBase, { TopBaseProps } from '../lib/TopBase';
import iconFile from '../../assets/images/icon_files.gif';
import { ItemFilesSubTypes } from '../../lib/ItemUtil';
import TopBase, { type TopBaseProps } from '../lib/TopBase';
class FilesTop extends TopBase {
constructor(props: TopBaseProps) {
super(props);
this.type = 'files';
this.label = 'Files';
this.icon = iconFile;
this.description = '[en]Various type of File.[/en][ja]ファイル[/ja]';
this.subTypes = ItemFilesSubTypes;
}
constructor(props: TopBaseProps) {
super(props);
this.type = 'files';
this.label = 'Files';
this.icon = iconFile;
this.description = '[en]Various type of File.[/en][ja]ファイル[/ja]';
this.subTypes = ItemFilesSubTypes;
}
}
export default FilesTop;
+4 -4
View File
@@ -4,10 +4,10 @@ import FilesList from './FilesList';
import FilesTop from './FilesTop';
const ItemTypeFiles = {
Top: FilesTop,
List: FilesList,
Detail: FilesDetail,
AdvancedSearch: FilesAdvancedSearch,
Top: FilesTop,
List: FilesList,
Detail: FilesDetail,
AdvancedSearch: FilesAdvancedSearch,
};
export default ItemTypeFiles;
+157 -159
View File
@@ -1,20 +1,20 @@
import { BrainAtlasType, MultiLang } from '../../config';
import AdvancedSearchQuery from '../lib/AdvancedSearchQuery';
import {
Item,
ItemBinder,
ItemBook,
ItemConference,
ItemData,
ItemFiles,
ItemMemo,
ItemModel,
ItemPaper,
ItemPresentation,
ItemSimulator,
ItemStimulus,
ItemTool,
ItemUrl,
import type { BrainAtlasType, MultiLang } from '../../config';
import type AdvancedSearchQuery from '../lib/AdvancedSearchQuery';
import type {
Item,
ItemBinder,
ItemBook,
ItemConference,
ItemData,
ItemFiles,
ItemMemo,
ItemModel,
ItemPaper,
ItemPresentation,
ItemSimulator,
ItemStimulus,
ItemTool,
ItemUrl,
} from '../lib/ItemUtil';
import ItemTypeBinder from './binder';
import ItemTypeBook from './book';
@@ -31,168 +31,166 @@ import ItemTypeTool from './tool';
import ItemTypeUrl from './url';
interface TopProps {
lang: MultiLang;
itemType: string;
type: BrainAtlasType;
lang: MultiLang;
itemType: string;
type: BrainAtlasType;
}
const Top = (props: TopProps) => {
const { lang, itemType, type } = props;
switch (itemType) {
case 'xnpbinder':
return <ItemTypeBinder.Top lang={lang} type={type} />;
case 'xnpbook':
return <ItemTypeBook.Top lang={lang} type={type} />;
case 'xnpconference':
return <ItemTypeConference.Top lang={lang} type={type} />;
case 'xnpdata':
return <ItemTypeData.Top lang={lang} type={type} />;
case 'xnpfiles':
return <ItemTypeFiles.Top lang={lang} type={type} />;
case 'xnpmemo':
return <ItemTypeMemo.Top lang={lang} type={type} />;
case 'xnpmodel':
return <ItemTypeModel.Top lang={lang} type={type} />;
case 'xnppaper':
return <ItemTypePaper.Top lang={lang} type={type} />;
case 'xnppresentation':
return <ItemTypePresentation.Top lang={lang} type={type} />;
case 'xnpsimulator':
return <ItemTypeSimulator.Top lang={lang} type={type} />;
case 'xnpstimulus':
return <ItemTypeStimulus.Top lang={lang} type={type} />;
case 'xnptool':
return <ItemTypeTool.Top lang={lang} type={type} />;
case 'xnpurl':
return <ItemTypeUrl.Top lang={lang} type={type} />;
default:
return null;
}
const { lang, itemType, type } = props;
switch (itemType) {
case 'xnpbinder':
return <ItemTypeBinder.Top lang={lang} type={type} />;
case 'xnpbook':
return <ItemTypeBook.Top lang={lang} type={type} />;
case 'xnpconference':
return <ItemTypeConference.Top lang={lang} type={type} />;
case 'xnpdata':
return <ItemTypeData.Top lang={lang} type={type} />;
case 'xnpfiles':
return <ItemTypeFiles.Top lang={lang} type={type} />;
case 'xnpmemo':
return <ItemTypeMemo.Top lang={lang} type={type} />;
case 'xnpmodel':
return <ItemTypeModel.Top lang={lang} type={type} />;
case 'xnppaper':
return <ItemTypePaper.Top lang={lang} type={type} />;
case 'xnppresentation':
return <ItemTypePresentation.Top lang={lang} type={type} />;
case 'xnpsimulator':
return <ItemTypeSimulator.Top lang={lang} type={type} />;
case 'xnpstimulus':
return <ItemTypeStimulus.Top lang={lang} type={type} />;
case 'xnptool':
return <ItemTypeTool.Top lang={lang} type={type} />;
case 'xnpurl':
return <ItemTypeUrl.Top lang={lang} type={type} />;
default:
return null;
}
};
interface ListProps {
lang: MultiLang;
item: Item;
type: BrainAtlasType;
lang: MultiLang;
item: Item;
type: BrainAtlasType;
}
const List = (props: ListProps) => {
const { lang, item, type } = props;
switch (item.item_type_name) {
case 'xnpbinder':
return <ItemTypeBinder.List lang={lang} item={item as ItemBinder} type={type} />;
case 'xnpbook':
return <ItemTypeBook.List lang={lang} item={item as ItemBook} type={type} />;
case 'xnpconference':
return <ItemTypeConference.List lang={lang} item={item as ItemConference} type={type} />;
case 'xnpdata':
return <ItemTypeData.List lang={lang} item={item as ItemData} type={type} />;
case 'xnpfiles':
return <ItemTypeFiles.List lang={lang} item={item as ItemFiles} type={type} />;
case 'xnpmemo':
return <ItemTypeMemo.List lang={lang} item={item as ItemMemo} type={type} />;
case 'xnpmodel':
return <ItemTypeModel.List lang={lang} item={item as ItemModel} type={type} />;
case 'xnppaper':
return <ItemTypePaper.List lang={lang} item={item as ItemPaper} type={type} />;
case 'xnppresentation':
return <ItemTypePresentation.List lang={lang} item={item as ItemPresentation} type={type} />;
case 'xnpsimulator':
return <ItemTypeSimulator.List lang={lang} item={item as ItemSimulator} type={type} />;
case 'xnpstimulus':
return <ItemTypeStimulus.List lang={lang} item={item as ItemStimulus} type={type} />;
case 'xnptool':
return <ItemTypeTool.List lang={lang} item={item as ItemTool} type={type} />;
case 'xnpurl':
return <ItemTypeUrl.List lang={lang} item={item as ItemUrl} type={type} />;
default:
return null;
}
const { lang, item, type } = props;
switch (item.item_type_name) {
case 'xnpbinder':
return <ItemTypeBinder.List lang={lang} item={item as ItemBinder} type={type} />;
case 'xnpbook':
return <ItemTypeBook.List lang={lang} item={item as ItemBook} type={type} />;
case 'xnpconference':
return <ItemTypeConference.List lang={lang} item={item as ItemConference} type={type} />;
case 'xnpdata':
return <ItemTypeData.List lang={lang} item={item as ItemData} type={type} />;
case 'xnpfiles':
return <ItemTypeFiles.List lang={lang} item={item as ItemFiles} type={type} />;
case 'xnpmemo':
return <ItemTypeMemo.List lang={lang} item={item as ItemMemo} type={type} />;
case 'xnpmodel':
return <ItemTypeModel.List lang={lang} item={item as ItemModel} type={type} />;
case 'xnppaper':
return <ItemTypePaper.List lang={lang} item={item as ItemPaper} type={type} />;
case 'xnppresentation':
return <ItemTypePresentation.List lang={lang} item={item as ItemPresentation} type={type} />;
case 'xnpsimulator':
return <ItemTypeSimulator.List lang={lang} item={item as ItemSimulator} type={type} />;
case 'xnpstimulus':
return <ItemTypeStimulus.List lang={lang} item={item as ItemStimulus} type={type} />;
case 'xnptool':
return <ItemTypeTool.List lang={lang} item={item as ItemTool} type={type} />;
case 'xnpurl':
return <ItemTypeUrl.List lang={lang} item={item as ItemUrl} type={type} />;
default:
return null;
}
};
interface DetailProps {
lang: MultiLang;
item: Item;
type: BrainAtlasType;
lang: MultiLang;
item: Item;
type: BrainAtlasType;
}
const Detail = (props: DetailProps) => {
const { lang, item, type } = props;
switch (item.item_type_name) {
case 'xnpbinder':
return <ItemTypeBinder.Detail lang={lang} item={item as ItemBinder} type={type} />;
case 'xnpbook':
return <ItemTypeBook.Detail lang={lang} item={item as ItemBook} type={type} />;
case 'xnpconference':
return <ItemTypeConference.Detail lang={lang} item={item as ItemConference} type={type} />;
case 'xnpdata':
return <ItemTypeData.Detail lang={lang} item={item as ItemData} type={type} />;
case 'xnpfiles':
return <ItemTypeFiles.Detail lang={lang} item={item as ItemFiles} type={type} />;
case 'xnpmemo':
return <ItemTypeMemo.Detail lang={lang} item={item as ItemMemo} type={type} />;
case 'xnpmodel':
return <ItemTypeModel.Detail lang={lang} item={item as ItemModel} type={type} />;
case 'xnppaper':
return <ItemTypePaper.Detail lang={lang} item={item as ItemPaper} type={type} />;
case 'xnppresentation':
return (
<ItemTypePresentation.Detail lang={lang} item={item as ItemPresentation} type={type} />
);
case 'xnpsimulator':
return <ItemTypeSimulator.Detail lang={lang} item={item as ItemSimulator} type={type} />;
case 'xnpstimulus':
return <ItemTypeStimulus.Detail lang={lang} item={item as ItemStimulus} type={type} />;
case 'xnptool':
return <ItemTypeTool.Detail lang={lang} item={item as ItemTool} type={type} />;
case 'xnpurl':
return <ItemTypeUrl.Detail lang={lang} item={item as ItemUrl} type={type} />;
default:
return null;
}
const { lang, item, type } = props;
switch (item.item_type_name) {
case 'xnpbinder':
return <ItemTypeBinder.Detail lang={lang} item={item as ItemBinder} type={type} />;
case 'xnpbook':
return <ItemTypeBook.Detail lang={lang} item={item as ItemBook} type={type} />;
case 'xnpconference':
return <ItemTypeConference.Detail lang={lang} item={item as ItemConference} type={type} />;
case 'xnpdata':
return <ItemTypeData.Detail lang={lang} item={item as ItemData} type={type} />;
case 'xnpfiles':
return <ItemTypeFiles.Detail lang={lang} item={item as ItemFiles} type={type} />;
case 'xnpmemo':
return <ItemTypeMemo.Detail lang={lang} item={item as ItemMemo} type={type} />;
case 'xnpmodel':
return <ItemTypeModel.Detail lang={lang} item={item as ItemModel} type={type} />;
case 'xnppaper':
return <ItemTypePaper.Detail lang={lang} item={item as ItemPaper} type={type} />;
case 'xnppresentation':
return <ItemTypePresentation.Detail lang={lang} item={item as ItemPresentation} type={type} />;
case 'xnpsimulator':
return <ItemTypeSimulator.Detail lang={lang} item={item as ItemSimulator} type={type} />;
case 'xnpstimulus':
return <ItemTypeStimulus.Detail lang={lang} item={item as ItemStimulus} type={type} />;
case 'xnptool':
return <ItemTypeTool.Detail lang={lang} item={item as ItemTool} type={type} />;
case 'xnpurl':
return <ItemTypeUrl.Detail lang={lang} item={item as ItemUrl} type={type} />;
default:
return null;
}
};
interface AdvancedSearchProps {
lang: MultiLang;
type: string;
query: AdvancedSearchQuery;
lang: MultiLang;
type: string;
query: AdvancedSearchQuery;
}
const AdvancedSearch = (props: AdvancedSearchProps) => {
const { lang, type, query } = props;
switch (type) {
case 'xnpbinder':
return <ItemTypeBinder.AdvancedSearch lang={lang} query={query} />;
case 'xnpbook':
return <ItemTypeBook.AdvancedSearch lang={lang} query={query} />;
case 'xnpconference':
return <ItemTypeConference.AdvancedSearch lang={lang} query={query} />;
case 'xnpdata':
return <ItemTypeData.AdvancedSearch lang={lang} query={query} />;
case 'xnpfiles':
return <ItemTypeFiles.AdvancedSearch lang={lang} query={query} />;
case 'xnpmemo':
return <ItemTypeMemo.AdvancedSearch lang={lang} query={query} />;
case 'xnpmodel':
return <ItemTypeModel.AdvancedSearch lang={lang} query={query} />;
case 'xnppaper':
return <ItemTypePaper.AdvancedSearch lang={lang} query={query} />;
case 'xnppresentation':
return <ItemTypePresentation.AdvancedSearch lang={lang} query={query} />;
case 'xnpsimulator':
return <ItemTypeSimulator.AdvancedSearch lang={lang} query={query} />;
case 'xnpstimulus':
return <ItemTypeStimulus.AdvancedSearch lang={lang} query={query} />;
case 'xnptool':
return <ItemTypeTool.AdvancedSearch lang={lang} query={query} />;
case 'xnpurl':
return <ItemTypeUrl.AdvancedSearch lang={lang} query={query} />;
default:
return null;
}
const { lang, type, query } = props;
switch (type) {
case 'xnpbinder':
return <ItemTypeBinder.AdvancedSearch lang={lang} query={query} />;
case 'xnpbook':
return <ItemTypeBook.AdvancedSearch lang={lang} query={query} />;
case 'xnpconference':
return <ItemTypeConference.AdvancedSearch lang={lang} query={query} />;
case 'xnpdata':
return <ItemTypeData.AdvancedSearch lang={lang} query={query} />;
case 'xnpfiles':
return <ItemTypeFiles.AdvancedSearch lang={lang} query={query} />;
case 'xnpmemo':
return <ItemTypeMemo.AdvancedSearch lang={lang} query={query} />;
case 'xnpmodel':
return <ItemTypeModel.AdvancedSearch lang={lang} query={query} />;
case 'xnppaper':
return <ItemTypePaper.AdvancedSearch lang={lang} query={query} />;
case 'xnppresentation':
return <ItemTypePresentation.AdvancedSearch lang={lang} query={query} />;
case 'xnpsimulator':
return <ItemTypeSimulator.AdvancedSearch lang={lang} query={query} />;
case 'xnpstimulus':
return <ItemTypeStimulus.AdvancedSearch lang={lang} query={query} />;
case 'xnptool':
return <ItemTypeTool.AdvancedSearch lang={lang} query={query} />;
case 'xnpurl':
return <ItemTypeUrl.AdvancedSearch lang={lang} query={query} />;
default:
return null;
}
};
const ItemType = {
Top,
List,
Detail,
AdvancedSearch,
Top,
List,
Detail,
AdvancedSearch,
};
export default ItemType;
+192 -201
View File
@@ -1,231 +1,222 @@
import React from 'react';
import { MultiLang } from '../../../config';
import type { MultiLang } from '../../../config';
import Functions from '../../../functions';
import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
import { ItemSubTypesAll } from '../../lib/ItemUtil';
import type AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
import type { ItemSubTypesAll } from '../../lib/ItemUtil';
export interface AdvancedSearchBaseProps {
lang: MultiLang;
query: AdvancedSearchQuery;
lang: MultiLang;
query: AdvancedSearchQuery;
}
interface State {
show: boolean;
values: Record<string, string>;
show: boolean;
values: Record<string, string>;
}
class AdvancedSearchBase extends React.Component<AdvancedSearchBaseProps, State> {
protected type = 'base';
protected title = 'Base';
protected query: AdvancedSearchQuery;
protected ignoreKeys: string[] = [];
protected type = 'base';
protected title = 'Base';
protected query: AdvancedSearchQuery;
protected ignoreKeys: string[] = [];
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.state = {
show: false,
values: {},
};
this.query = props.query;
this.handleChangeTitleCheck = this.handleChangeTitleCheck.bind(this);
}
updateQuery(key: string, value: string) {
if (this.ignoreKeys.includes(key)) {
this.query.delete(this.type, key);
} else {
this.query.set(this.type, key, value);
constructor(props: AdvancedSearchBaseProps) {
super(props);
this.state = {
show: false,
values: {},
};
this.query = props.query;
this.handleChangeTitleCheck = this.handleChangeTitleCheck.bind(this);
}
}
setIgnoreKey(key: string) {
if (!this.ignoreKeys.includes(key)) {
this.ignoreKeys = this.ignoreKeys.concat(key);
updateQuery(key: string, value: string) {
if (this.ignoreKeys.includes(key)) {
this.query.delete(this.type, key);
} else {
this.query.set(this.type, key, value);
}
}
this.query.delete(this.type, key);
}
deleteIgnoreKey(key: string) {
if (this.ignoreKeys.includes(key)) {
this.ignoreKeys = this.ignoreKeys.filter((v) => {
return v !== key;
});
setIgnoreKey(key: string) {
if (!this.ignoreKeys.includes(key)) {
this.ignoreKeys = this.ignoreKeys.concat(key);
}
this.query.delete(this.type, key);
}
this.query.set(this.type, key, this.state.values[key]);
}
updateField(key: string, value: string) {
const values = Object.assign({}, this.state.values);
values[key] = value;
this.updateQuery(key, value);
this.setState({ values });
}
deleteIgnoreKey(key: string) {
if (this.ignoreKeys.includes(key)) {
this.ignoreKeys = this.ignoreKeys.filter((v) => {
return v !== key;
});
}
this.query.set(this.type, key, this.state.values[key]);
}
handleChangeTitleCheck(e: React.ChangeEvent<HTMLInputElement>) {
const show = e.target.checked;
if (show) {
Object.keys(this.state.values).forEach((key) => {
const value = this.state.values[key];
updateField(key: string, value: string) {
const values = Object.assign({}, this.state.values);
values[key] = value;
this.updateQuery(key, value);
});
} else {
this.query.deleteType(this.type);
this.setState({ values });
}
this.setState({ show });
}
getRows(): { label: string; value: JSX.Element }[] {
return [];
}
renderFieldInputText(key: string, size: number) {
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
this.updateField(key, e.target.value);
};
return (
<input
className="fieldInputText"
type="text"
value={this.state.values[key]}
size={size}
onChange={onChange}
/>
);
}
renderFieldSelect(key: string, values: ItemSubTypesAll) {
const onChange: React.ChangeEventHandler<HTMLSelectElement> = (e) => {
this.updateField(key, e.target.value);
};
const options = values.map(({ type, label }, i) => {
return (
<option key={i} value={type}>
{label}
</option>
);
});
return (
<select className="fieldSelect" value={this.state.values[key]} onChange={onChange}>
<option value="">Any</option>
{options}
</select>
);
}
renderFieldDate(label: string, keyYear: string, keyMonth: string, keyMday: string) {
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
if (e.target.checked) {
this.deleteIgnoreKey(keyYear);
keyMonth !== '' && this.deleteIgnoreKey(keyMonth);
keyMday !== '' && this.deleteIgnoreKey(keyMday);
} else {
this.setIgnoreKey(keyYear);
keyMonth !== '' && this.setIgnoreKey(keyMonth);
keyMday !== '' && this.setIgnoreKey(keyMday);
}
};
const month = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const monthOptions = month.map((value, i) => {
return (
<option key={i} value={i + 1}>
{value}
</option>
);
});
const mdayOptions: JSX.Element[] = [];
for (let i = 1; i <= 31; i++) {
mdayOptions.push(
<option key={i} value={i}>
{i}
</option>,
);
handleChangeTitleCheck(e: React.ChangeEvent<HTMLInputElement>) {
const show = e.target.checked;
if (show) {
Object.keys(this.state.values).forEach((key) => {
const value = this.state.values[key];
this.updateQuery(key, value);
});
} else {
this.query.deleteType(this.type);
}
this.setState({ show });
}
return (
<div className="fieldDate">
<input type="checkbox" onChange={onChange} />
{label.length !== 0 && <label className="fieldDateLabel">{label}</label>}
{keyMonth !== '' && (
<select
value={this.state.values[keyMonth]}
onChange={(e) => this.updateField(keyMonth, e.target.value)}
>
{monthOptions}
</select>
)}
{keyMday !== '' && (
<select
value={this.state.values[keyMday]}
onChange={(e) => this.updateField(keyMday, e.target.value)}
>
{mdayOptions}
</select>
)}
<input
type="text"
value={this.state.values[keyYear]}
size={5}
onChange={(e) => this.updateField(keyYear, e.target.value)}
/>
</div>
);
}
renderBody() {
const { lang } = this.props;
if (this.state.show === false) {
return null;
getRows(): { label: string; value: React.JSX.Element }[] {
return [];
}
const rows = this.getRows();
const fields = rows.map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
return (
<tr key={idx}>
<td className="head">{Functions.mlang(value.label, lang)}</td>
<td className={evenodd}>{value.value}</td>
</tr>
);
});
return (
<table className="itemtypeFields outer">
<tbody>{fields}</tbody>
</table>
);
}
render() {
return (
<div className="itemtype">
<table className="itemtypeName outer">
<tbody>
<tr>
<th align="left">
renderFieldInputText(key: string, size: number) {
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
this.updateField(key, e.target.value);
};
return (
<input
className="fieldInputText"
type="text"
value={this.state.values[key]}
size={size}
onChange={onChange}
/>
);
}
renderFieldSelect(key: string, values: ItemSubTypesAll) {
const onChange: React.ChangeEventHandler<HTMLSelectElement> = (e) => {
this.updateField(key, e.target.value);
};
const options = values.map(({ type, label }, _i) => {
return (
<option key={type} value={type}>
{label}
</option>
);
});
return (
<select className="fieldSelect" value={this.state.values[key]} onChange={onChange}>
<option value="">Any</option>
{options}
</select>
);
}
renderFieldDate(label: string, keyYear: string, keyMonth: string, keyMday: string) {
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
if (e.target.checked) {
this.deleteIgnoreKey(keyYear);
keyMonth !== '' && this.deleteIgnoreKey(keyMonth);
keyMday !== '' && this.deleteIgnoreKey(keyMday);
} else {
this.setIgnoreKey(keyYear);
keyMonth !== '' && this.setIgnoreKey(keyMonth);
keyMday !== '' && this.setIgnoreKey(keyMday);
}
};
const month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const monthOptions = month.map((value, i) => {
return (
<option key={value} value={i + 1}>
{value}
</option>
);
});
const mdayOptions: React.JSX.Element[] = [];
for (let i = 1; i <= 31; i++) {
mdayOptions.push(
<option key={i} value={i}>
{i}
</option>
);
}
return (
<div className="fieldDate">
<input type="checkbox" id={keyYear} onChange={onChange} />
{label.length !== 0 && (
<label className="fieldDateLabel" htmlFor={keyYear}>
{label}
</label>
)}
{keyMonth !== '' && (
<select
value={this.state.values[keyMonth]}
onChange={(e) => this.updateField(keyMonth, e.target.value)}
>
{monthOptions}
</select>
)}
{keyMday !== '' && (
<select
value={this.state.values[keyMday]}
onChange={(e) => this.updateField(keyMday, e.target.value)}
>
{mdayOptions}
</select>
)}
<input
type="checkbox"
checked={this.state.show}
onChange={(e) => this.handleChangeTitleCheck(e)}
type="text"
value={this.state.values[keyYear]}
size={5}
onChange={(e) => this.updateField(keyYear, e.target.value)}
/>
{this.title}
</th>
</tr>
</tbody>
</table>
{this.renderBody()}
</div>
);
}
</div>
);
}
renderBody() {
const { lang } = this.props;
if (this.state.show === false) {
return null;
}
const rows = this.getRows();
const fields = rows.map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
return (
<tr key={value.label}>
<td className="head">{Functions.mlang(value.label, lang)}</td>
<td className={evenodd}>{value.value}</td>
</tr>
);
});
return (
<table className="itemtypeFields outer">
<tbody>{fields}</tbody>
</table>
);
}
render() {
return (
<div className="itemtype">
<table className="itemtypeName outer">
<tbody>
<tr>
<th align="left">
<input
type="checkbox"
checked={this.state.show}
onChange={(e) => this.handleChangeTitleCheck(e)}
/>
{this.title}
</th>
</tr>
</tbody>
</table>
{this.renderBody()}
</div>
);
}
}
export default AdvancedSearchBase;
+27 -27
View File
@@ -1,42 +1,42 @@
import React from 'react';
import { BrainAtlasType, MultiLang } from '../../../config';
import type { BrainAtlasType, MultiLang } from '../../../config';
import Functions from '../../../functions';
import { Item } from '../../lib/ItemUtil';
import type { Item } from '../../lib/ItemUtil';
export interface DetailBaseField {
label: string;
value: React.ReactNode;
label: string;
value: React.ReactNode;
}
export interface DetailBaseProps {
lang: MultiLang;
item: Item;
type: BrainAtlasType;
lang: MultiLang;
item: Item;
type: BrainAtlasType;
}
class DetailBase extends React.Component<DetailBaseProps> {
getFields(): DetailBaseField[] {
return [];
}
getFields(): DetailBaseField[] {
return [];
}
render() {
const { lang } = this.props;
const elements = this.getFields().map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
return (
<tr key={idx}>
<td className="head">{Functions.mlang(value.label, lang)}</td>
<td className={evenodd}>{value.value}</td>
</tr>
);
});
return (
<table className="outer itemDetail">
<tbody>{elements}</tbody>
</table>
);
}
render() {
const { lang } = this.props;
const elements = this.getFields().map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
return (
<tr key={value.label}>
<td className="head">{Functions.mlang(value.label, lang)}</td>
<td className={evenodd}>{value.value}</td>
</tr>
);
});
return (
<table className="outer itemDetail">
<tbody>{elements}</tbody>
</table>
);
}
}
export default DetailBase;
+35 -35
View File
@@ -1,49 +1,49 @@
import React from 'react';
import { BrainAtlasType, MultiLang } from '../../../config';
import ItemUtil, { Item } from '../../lib/ItemUtil';
import type { BrainAtlasType, MultiLang } from '../../../config';
import ItemUtil, { type Item } from '../../lib/ItemUtil';
import SimPFLinkIcon from './field/SimPFLinkIcon';
export interface ListBaseProps {
lang: MultiLang;
item: Item;
type: BrainAtlasType;
lang: MultiLang;
item: Item;
type: BrainAtlasType;
}
class ListBase extends React.Component<ListBaseProps> {
protected label = '';
protected icon = '';
protected url: string;
protected simpfLinkUrl: string;
protected label = '';
protected icon = '';
protected url: string;
protected simpfLinkUrl: string;
constructor(props: ListBaseProps) {
super(props);
this.url = ItemUtil.getUrl(props.type, props.item);
this.simpfLinkUrl = ItemUtil.getSimPFLinkUrl(props.item.item_id);
}
constructor(props: ListBaseProps) {
super(props);
this.url = ItemUtil.getUrl(props.type, props.item);
this.simpfLinkUrl = ItemUtil.getSimPFLinkUrl(props.item.item_id);
}
renderBody() {
return <></>;
}
renderBody(): React.ReactNode {
return null;
}
render() {
const { lang } = this.props;
return (
<table>
<tbody>
<tr>
<td className="listIcon">
<img src={this.icon} alt={this.label} />
</td>
<td>{this.renderBody()}</td>
<td className="listExtra">
<SimPFLinkIcon lang={lang} url={this.simpfLinkUrl} isDetail={false} />
</td>
</tr>
</tbody>
</table>
);
}
render() {
const { lang } = this.props;
return (
<table>
<tbody>
<tr>
<td className="listIcon">
<img src={this.icon} alt={this.label} />
</td>
<td>{this.renderBody()}</td>
<td className="listExtra">
<SimPFLinkIcon lang={lang} url={this.simpfLinkUrl} isDetail={false} />
</td>
</tr>
</tbody>
</table>
);
}
}
export default ListBase;
+42 -42
View File
@@ -1,54 +1,54 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../../../config';
import { Link } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../../../config';
import Functions from '../../../functions';
import ItemUtil, { ItemSubTypesAll } from '../../lib/ItemUtil';
import ItemUtil, { type ItemSubTypesAll } from '../../lib/ItemUtil';
export interface TopBaseProps {
lang: MultiLang;
type: BrainAtlasType;
lang: MultiLang;
type: BrainAtlasType;
}
class TopBase extends React.Component<TopBaseProps> {
protected type = '';
protected label = '';
protected icon = '';
protected description = '';
protected subTypes: ItemSubTypesAll = [];
protected type = '';
protected label = '';
protected icon = '';
protected description = '';
protected subTypes: ItemSubTypesAll = [];
render() {
const { lang, type } = this.props;
const url = ItemUtil.getItemTypeSearchUrl(type, this.type, '');
const links = this.subTypes.map((subtype, i) => {
const url = ItemUtil.getItemTypeSearchUrl(type, this.type, subtype.type);
return (
<React.Fragment key={i}>
{i > 0 && ' / '}
<Link to={url}>{subtype.label}</Link>
</React.Fragment>
);
});
return (
<div>
<table>
<tbody>
<tr>
<td>
<img src={this.icon} alt={this.label} />
</td>
<td className="itemTypeName">
<Link to={url}>{this.label}</Link>
</td>
</tr>
</tbody>
</table>
<hr />
<div>{Functions.mlang(this.description, lang)}</div>
{links}
</div>
);
}
render() {
const { lang, type } = this.props;
const url = ItemUtil.getItemTypeSearchUrl(type, this.type, '');
const links = this.subTypes.map((subtype, i) => {
const url = ItemUtil.getItemTypeSearchUrl(type, this.type, subtype.type);
return (
<React.Fragment key={subtype.type}>
{i > 0 && ' / '}
<Link to={url}>{subtype.label}</Link>
</React.Fragment>
);
});
return (
<div>
<table>
<tbody>
<tr>
<td>
<img src={this.icon} alt={this.label} />
</td>
<td className="itemTypeName">
<Link to={url}>{this.label}</Link>
</td>
</tr>
</tbody>
</table>
<hr />
<div>{Functions.mlang(this.description, lang)}</div>
{links}
</div>
);
}
}
export default TopBase;
+19 -19
View File
@@ -1,30 +1,30 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
interface Props {
lang: MultiLang;
author: string[];
lang: MultiLang;
author: string[];
}
const Author: React.FC<Props> = (props) => {
const { author } = props;
if (author.length === 0) {
return null;
}
const elements = author.map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
const { author } = props;
if (author.length === 0) {
return null;
}
const elements = author.map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
return (
<tr key={value}>
<td className={evenodd}>{value}</td>
</tr>
);
});
return (
<tr key={idx}>
<td className={evenodd}>{value}</td>
</tr>
<table>
<tbody>{elements}</tbody>
</table>
);
});
return (
<table>
<tbody>{elements}</tbody>
</table>
);
};
export default Author;
+22 -22
View File
@@ -1,35 +1,35 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
import Functions from '../../../../functions';
import { ItemBasicChangeLog } from '../../../lib/ItemUtil';
import type { ItemBasicChangeLog } from '../../../lib/ItemUtil';
import DateTime from './DateTime';
interface Props {
lang: MultiLang;
changelog: ItemBasicChangeLog[];
lang: MultiLang;
changelog: ItemBasicChangeLog[];
}
const ChangeLog: React.FC<Props> = (props) => {
const { lang, changelog } = props;
if (changelog.length === 0) {
return null;
}
const elements = changelog.map((value, i) => {
const { lang, changelog } = props;
if (changelog.length === 0) {
return null;
}
const elements = changelog.map((value, _i) => {
return (
<tr key={`${value.log_date}:${value.log}`}>
<td>
<DateTime lang={lang} date={value.log_date} onlyDate={true} />
</td>
<td>{Functions.mlang(value.log, lang)}</td>
</tr>
);
});
return (
<tr key={i}>
<td>
<DateTime lang={lang} date={value.log_date} onlyDate={true} />
</td>
<td>{Functions.mlang(value.log, lang)}</td>
</tr>
<table>
<tbody>{elements}</tbody>
</table>
);
});
return (
<table>
<tbody>{elements}</tbody>
</table>
);
};
export default ChangeLog;
@@ -1,19 +1,19 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
import Functions from '../../../../functions';
interface Props {
lang: MultiLang;
uname: string;
name: string;
lang: MultiLang;
uname: string;
name: string;
}
const Contributor: React.FC<Props> = (props) => {
const { lang, name, uname } = props;
const unsubscribed = '([en]Unsubscribed User[/en][ja]退会済みユーザ[/ja])';
const label = uname === '' ? unsubscribed : name === '' ? uname : name + ' (' + uname + ')';
return <span>{Functions.mlang(label, lang)}</span>;
const { lang, name, uname } = props;
const unsubscribed = '([en]Unsubscribed User[/en][ja]退会済みユーザ[/ja])';
const label = uname === '' ? unsubscribed : name === '' ? uname : `${name} (${uname})`;
return <span>{Functions.mlang(label, lang)}</span>;
};
export default Contributor;
@@ -1,75 +1,71 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
type CreativeCommonsType = 'by' | 'by-nc' | 'by-nc-nd' | 'by-nc-sa' | 'by-nd' | 'by-sa';
const getCreativeCommonsType = (
ccCommercialUse: number,
ccModification: number,
): CreativeCommonsType => {
const cc = ccCommercialUse * 10 + ccModification;
switch (cc) {
case 0:
return 'by-nc-nd';
case 1:
return 'by-nc-sa';
case 2:
return 'by-nc';
case 10:
return 'by-nd';
case 11:
return 'by-sa';
case 12:
default:
return 'by';
}
const getCreativeCommonsType = (ccCommercialUse: number, ccModification: number): CreativeCommonsType => {
const cc = ccCommercialUse * 10 + ccModification;
switch (cc) {
case 0:
return 'by-nc-nd';
case 1:
return 'by-nc-sa';
case 2:
return 'by-nc';
case 10:
return 'by-nd';
case 11:
return 'by-sa';
default:
return 'by';
}
};
interface Props {
lang: MultiLang;
ccCommercialUse: number;
ccModification: number;
lang: MultiLang;
ccCommercialUse: number;
ccModification: number;
}
const CreativeCommons: React.FC<Props> = (props) => {
const { ccCommercialUse, ccModification } = props;
const type = getCreativeCommonsType(ccCommercialUse, ccModification);
const url = 'http://creativecommons.org/licenses/' + type + '/4.0/';
const logoUrl = 'https://i.creativecommons.org/l/' + type + '/4.0/88x31.png';
const labels = {
by: 'Attribution',
nc: 'NonCommercial',
nd: 'NoDerivatives',
sa: 'ShareAlike',
};
const label = type
.split('-')
.map((value) => {
const prop = value as 'by' | 'nc' | 'nd' | 'sa';
return labels[prop];
})
.join('-');
return (
<table style={{ borderCollapse: 'separate', borderSpacing: '5px' }}>
<tbody>
<tr>
<td>
<a href={url} target="_blank" rel="license noopener noreferrer">
<img alt="Creative Commons License" src={logoUrl} />
</a>
</td>
<td>
This work is licensed under a{' '}
<a href={url} target="_blank" rel="license noopener noreferrer">
Creative Commons {label} 4.0 International License
</a>
.
</td>
</tr>
</tbody>
</table>
);
const { ccCommercialUse, ccModification } = props;
const type = getCreativeCommonsType(ccCommercialUse, ccModification);
const url = `http://creativecommons.org/licenses/${type}/4.0/`;
const logoUrl = `https://i.creativecommons.org/l/${type}/4.0/88x31.png`;
const labels = {
by: 'Attribution',
nc: 'NonCommercial',
nd: 'NoDerivatives',
sa: 'ShareAlike',
};
const label = type
.split('-')
.map((value) => {
const prop = value as 'by' | 'nc' | 'nd' | 'sa';
return labels[prop];
})
.join('-');
return (
<table style={{ borderCollapse: 'separate', borderSpacing: '5px' }}>
<tbody>
<tr>
<td>
<a href={url} target="_blank" rel="license noopener noreferrer">
<img alt="Creative Commons License" src={logoUrl} />
</a>
</td>
<td>
This work is licensed under a{' '}
<a href={url} target="_blank" rel="license noopener noreferrer">
Creative Commons {label} 4.0 International License
</a>
.
</td>
</tr>
</tbody>
</table>
);
};
export default CreativeCommons;
+9 -15
View File
@@ -1,22 +1,16 @@
import React from 'react';
import moment from 'moment';
import { MultiLang } from '../../../../config';
import { format } from 'date-fns';
import type { MultiLang } from '../../../../config';
interface Props {
lang: MultiLang;
date: number;
onlyDate?: boolean;
lang: MultiLang;
date: number;
onlyDate?: boolean;
}
const DateTime: React.FC<Props> = (props) => {
const { date, onlyDate } = props;
const d = moment(new Date(date * 1000));
let format = 'MMM D, Y';
if (typeof onlyDate === 'undefined' || !onlyDate) {
format += ' HH:mm:ss';
}
return <span>{d.format(format)}</span>;
const DateTime = (props: Props) => {
const { date, onlyDate } = props;
const pattern = onlyDate ? 'MMM d, y' : 'MMM d, y HH:mm:ss';
return <span>{format(new Date(date * 1000), pattern)}</span>;
};
export default DateTime;
@@ -1,19 +1,19 @@
import React from 'react';
import type React from 'react';
import XoopsCode from '../../../../common/lib/XoopsCode';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
interface Props {
lang: MultiLang;
description: string;
className?: string;
lang: MultiLang;
description: string;
className?: string;
}
const Description: React.FC<Props> = (props) => {
const { lang, description, className } = props;
const textarea = <XoopsCode lang={lang} text={description} dobr={true} />;
const name = typeof className === 'undefined' ? 'description' : className;
return <div className={name}>{textarea}</div>;
const { lang, description, className } = props;
const textarea = <XoopsCode lang={lang} text={description} dobr={true} />;
const name = typeof className === 'undefined' ? 'description' : className;
return <div className={name}>{textarea}</div>;
};
export default Description;
@@ -1,21 +1,21 @@
.downloadButton {
display: inline-block;
padding: 7px 20px;
text-decoration: none !important;
font-weight: normal !important;
background: #f0f0f0;
color: #000 !important;
border: solid 1px #e0e0e0;
box-shadow: 2px 2px #bbbbbb;
border-radius: 5px;
display: inline-block;
padding: 7px 20px;
text-decoration: none;
font-weight: normal;
background: #f0f0f0;
color: #000;
border: solid 1px #e0e0e0;
box-shadow: 2px 2px #bbbbbb;
border-radius: 5px;
}
.downloadButton:hover {
background: #e8e8e8 !important;
border: solid 1px #cccccc;
background: #e8e8e8;
border: solid 1px #cccccc;
}
.downloadButton:active {
transform: translate(2px, 2px);
box-shadow: none;
transform: translate(2px, 2px);
box-shadow: none;
}
@@ -1,60 +1,59 @@
import React from 'react';
import { BrainAtlasType, MultiLang } from '../../../../config';
import ItemUtil, { ItemBasicFile } from '../../../lib/ItemUtil';
import type { BrainAtlasType, MultiLang } from '../../../../config';
import ItemUtil, { type ItemBasicFile } from '../../../lib/ItemUtil';
import styles from './FileDownloadButton.module.css';
import LicenseAgreementDialog from './LicenseAgreementDialog';
import styles from './FileDownloadButton.module.css';
interface Props {
lang: MultiLang;
file: ItemBasicFile;
rights: string;
useCc: number;
ccCommercialUse: number;
ccModification: number;
type: BrainAtlasType;
lang: MultiLang;
file: ItemBasicFile;
rights: string;
useCc: number;
ccCommercialUse: number;
ccModification: number;
type: BrainAtlasType;
}
const FileDownloadButton: React.FC<Props> = (props) => {
const { lang, file, rights, useCc, ccCommercialUse, ccModification, type } = props;
const { lang, file, rights, useCc, ccCommercialUse, ccModification, type } = props;
const [show, setShow] = React.useState<boolean>(false);
const [show, setShow] = React.useState<boolean>(false);
const handleClickDownload: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
if (rights !== '') {
e.stopPropagation();
e.preventDefault();
setShow(true);
}
};
const handleClickDownload: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
if (rights !== '') {
e.stopPropagation();
e.preventDefault();
setShow(true);
}
};
const url = ItemUtil.getFileUrl(type, file);
return (
<>
<a
className={styles.downloadButton}
href={url}
download={file.original_file_name}
target="_blank"
rel="noopener noreferrer"
onClick={handleClickDownload}
>
Download
</a>
<LicenseAgreementDialog
lang={lang}
file={file}
rights={rights}
useCc={useCc}
ccCommercialUse={ccCommercialUse}
ccModification={ccModification}
show={show}
unsetShow={() => setShow(false)}
type={type}
/>
</>
);
const url = ItemUtil.getFileUrl(type, file);
return (
<>
<a
className={styles.downloadButton}
href={url}
download={file.original_file_name}
target="_blank"
rel="noopener noreferrer"
onClick={handleClickDownload}
>
Download
</a>
<LicenseAgreementDialog
lang={lang}
file={file}
rights={rights}
useCc={useCc}
ccCommercialUse={ccCommercialUse}
ccModification={ccModification}
show={show}
unsetShow={() => setShow(false)}
type={type}
/>
</>
);
};
export default FileDownloadButton;
+9 -9
View File
@@ -1,18 +1,18 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
interface Props {
lang: MultiLang;
size: number;
lang: MultiLang;
size: number;
}
const FileSize: React.FC<Props> = (props) => {
const { size } = props;
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const power = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0;
const label = Math.round((size / Math.pow(1024, power)) * 10) / 10 + ' ' + units[power];
return <span>{label}</span>;
const { size } = props;
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const power = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0;
const label = `${Math.round((size / 1024 ** power) * 10) / 10} ${units[power]}`;
return <span>{label}</span>;
};
export default FileSize;
+10 -10
View File
@@ -1,19 +1,19 @@
import React from 'react';
import type React from 'react';
import { MultiLang } from '../../../../config';
import type { MultiLang } from '../../../../config';
interface Props {
lang: MultiLang;
keyword: string[];
lang: MultiLang;
keyword: string[];
}
const FreeKeyword: React.FC<Props> = (props) => {
const { keyword } = props;
if (keyword.length === 0) {
return null;
}
const label = keyword.join(', ');
return <span>{label}</span>;
const { keyword } = props;
if (keyword.length === 0) {
return null;
}
const label = keyword.join(', ');
return <span>{label}</span>;
};
export default FreeKeyword;
+75 -77
View File
@@ -1,92 +1,90 @@
import React from 'react';
import type React from 'react';
import { BrainAtlasType, MultiLang } from '../../../../config';
import type { BrainAtlasType, MultiLang } from '../../../../config';
import Functions from '../../../../functions';
import { ItemBasicFile } from '../../../lib/ItemUtil';
import type { ItemBasicFile } from '../../../lib/ItemUtil';
import DateTime from './DateTime';
import FileDownloadButton from './FileDownloadButton';
import FileSize from './FileSize';
interface Props {
lang: MultiLang;
file: ItemBasicFile[];
fileType: string;
rights?: string;
useCc?: number;
ccCommercialUse?: number;
ccModification?: number;
downloadLimit?: number;
type: BrainAtlasType;
lang: MultiLang;
file: ItemBasicFile[];
fileType: string;
rights?: string;
useCc?: number;
ccCommercialUse?: number;
ccModification?: number;
downloadLimit?: number;
type: BrainAtlasType;
}
const ItemFile: React.FC<Props> = (props) => {
const {
lang,
file,
fileType,
type,
rights = '',
useCc = 0,
ccCommercialUse = 0,
ccModification = 0,
downloadLimit = 0,
} = props;
const {
lang,
file,
fileType,
type,
rights = '',
useCc = 0,
ccCommercialUse = 0,
ccModification = 0,
downloadLimit = 0,
} = props;
const data = file.find((value) => {
return value.file_type_name === fileType;
});
const data = file.find((value) => {
return value.file_type_name === fileType;
});
if (typeof data === 'undefined') {
return null;
}
const date = new Date(data.timestamp);
const timestamp = Math.floor(date.valueOf() / 1000);
return (
<div>
{data.original_file_name}
<br />
<table>
<tbody>
<tr>
<td>Type</td>
<td>: {data.mime_type}</td>
<td rowSpan={4}>
{downloadLimit === 0 && (
<FileDownloadButton
lang={lang}
file={data}
rights={rights}
useCc={useCc}
ccCommercialUse={ccCommercialUse}
ccModification={ccModification}
type={type}
/>
)}
</td>
</tr>
<tr>
<td>Size</td>
<td>
: <FileSize lang={lang} size={data.file_size} />
</td>
</tr>
<tr>
<td>Last updated</td>
<td>
: <DateTime lang={lang} date={timestamp} onlyDate={true} />
</td>
</tr>
</tbody>
</table>
{downloadLimit === 1 && (
<>
<br />(
{Functions.mlang('[en]File has been removed[/en][ja]ファイルは削除されました[/ja]', lang)}
)
</>
)}
</div>
);
if (typeof data === 'undefined') {
return null;
}
const date = new Date(data.timestamp);
const timestamp = Math.floor(date.valueOf() / 1000);
return (
<div>
{data.original_file_name}
<br />
<table>
<tbody>
<tr>
<td>Type</td>
<td>: {data.mime_type}</td>
<td rowSpan={4}>
{downloadLimit === 0 && (
<FileDownloadButton
lang={lang}
file={data}
rights={rights}
useCc={useCc}
ccCommercialUse={ccCommercialUse}
ccModification={ccModification}
type={type}
/>
)}
</td>
</tr>
<tr>
<td>Size</td>
<td>
: <FileSize lang={lang} size={data.file_size} />
</td>
</tr>
<tr>
<td>Last updated</td>
<td>
: <DateTime lang={lang} date={timestamp} onlyDate={true} />
</td>
</tr>
</tbody>
</table>
{downloadLimit === 1 && (
<>
<br />({Functions.mlang('[en]File has been removed[/en][ja]ファイルは削除されました[/ja]', lang)})
</>
)}
</div>
);
};
export default ItemFile;
+25 -25
View File
@@ -1,38 +1,38 @@
import React from 'react';
import type React from 'react';
import { Link } from 'react-router-dom';
import { BrainAtlasType, MultiLang } from '../../../../config';
import { Link } from 'react-router';
import type { BrainAtlasType, MultiLang } from '../../../../config';
import Functions from '../../../../functions';
import IndexUtil from '../../../lib/IndexUtil';
import { ItemBasicIndex } from '../../../lib/ItemUtil';
import type { ItemBasicIndex } from '../../../lib/ItemUtil';
interface Props {
lang: MultiLang;
index: ItemBasicIndex[];
type: BrainAtlasType;
lang: MultiLang;
index: ItemBasicIndex[];
type: BrainAtlasType;
}
const ItemIndex: React.FC<Props> = (props) => {
const { lang, index, type } = props;
if (index.length === 0) {
return null;
}
const elements = index.map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
const url = IndexUtil.getUrl(type, value.index_id);
const { lang, index, type } = props;
if (index.length === 0) {
return null;
}
const elements = index.map((value, idx) => {
const evenodd = idx % 2 === 0 ? 'even' : 'odd';
const url = IndexUtil.getUrl(type, value.index_id);
return (
<tr key={value.index_id}>
<td className={evenodd}>
<Link to={url}>{Functions.mlang(value.title, lang)}</Link>
</td>
</tr>
);
});
return (
<tr key={value.index_id}>
<td className={evenodd}>
<Link to={url}>{Functions.mlang(value.title, lang)}</Link>
</td>
</tr>
<table>
<tbody>{elements}</tbody>
</table>
);
});
return (
<table>
<tbody>{elements}</tbody>
</table>
);
};
export default ItemIndex;
+26 -26
View File
@@ -1,35 +1,35 @@
import React from 'react';
import { MultiLang } from '../../../../config';
import type React from 'react';
import type { MultiLang } from '../../../../config';
import Functions from '../../../../functions';
import { ItemBasicLang } from '../../../lib/ItemUtil';
import type { ItemBasicLang } from '../../../lib/ItemUtil';
interface Props {
lang: MultiLang;
itemLang: ItemBasicLang;
lang: MultiLang;
itemLang: ItemBasicLang;
}
const Language: React.FC<Props> = (props) => {
const { lang, itemLang } = props;
const langStr = {
eng: '[en]English[/en][ja]英語[/ja]',
jpn: '[en]Japanese[/en][ja]日本語[/ja]',
fra: '[en]French[/en][ja]フランス語[/ja]',
deu: '[en]German[/en][ja]ドイツ語[/ja]',
esl: '[en]Spanish[/en][ja]スペイン語[/ja]',
ita: '[en]Italian[/en][ja]イタリア語[/ja]',
dut: '[en]Dutch[/en][ja]オランダ語[/ja]',
sve: '[en]Swedish[/en][ja]スウェーデン語[/ja]',
nor: '[en]Norwegian[/en][ja]ノルウェー語[/ja]',
dan: '[en]Danish[/en][ja]デンマーク語[/ja]',
fin: '[en]Finnish[/en][ja]フィンランド語[/ja]',
por: '[en]Portuguese[/en][ja]ポルトガル語[/ja]',
chi: '[en]Chinese[/en][ja]中国語[/ja]',
kor: '[en]Korean[/en][ja]韓国語[/ja]',
};
if (!(itemLang in langStr)) {
return null;
}
return <span>{Functions.mlang(langStr[itemLang], lang)}</span>;
const { lang, itemLang } = props;
const langStr = {
eng: '[en]English[/en][ja]英語[/ja]',
jpn: '[en]Japanese[/en][ja]日本語[/ja]',
fra: '[en]French[/en][ja]フランス語[/ja]',
deu: '[en]German[/en][ja]ドイツ語[/ja]',
esl: '[en]Spanish[/en][ja]スペイン語[/ja]',
ita: '[en]Italian[/en][ja]イタリア語[/ja]',
dut: '[en]Dutch[/en][ja]オランダ語[/ja]',
sve: '[en]Swedish[/en][ja]スウェーデン語[/ja]',
nor: '[en]Norwegian[/en][ja]ノルウェー語[/ja]',
dan: '[en]Danish[/en][ja]デンマーク語[/ja]',
fin: '[en]Finnish[/en][ja]フィンランド語[/ja]',
por: '[en]Portuguese[/en][ja]ポルトガル語[/ja]',
chi: '[en]Chinese[/en][ja]中国語[/ja]',
kor: '[en]Korean[/en][ja]韓国語[/ja]',
};
if (!(itemLang in langStr)) {
return null;
}
return <span>{Functions.mlang(langStr[itemLang], lang)}</span>;
};
export default Language;

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