Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15c94f9c35
|
||
|
|
d1d947c313
|
||
|
|
5fb467a76c
|
||
|
|
6e6e9ea9d9
|
||
|
|
81d075579f
|
||
|
|
6098ce2445
|
||
|
|
dead6ab2ac
|
||
|
|
20dd4f2a72
|
||
|
|
00f375cd9e
|
||
|
|
a2564c5466
|
@@ -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
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"jawandarajbir.react-vscode-extension-pack",
|
||||
"esbenp.prettier-vscode",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Vite Debugger",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5173/",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-71
@@ -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"
|
||||
}
|
||||
Vendored
-40
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Generated
+2098
-5586
File diff suppressed because it is too large
Load Diff
+41
-56
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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}`}> </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="/">» 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 & 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}`}> </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="/">» 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 & Laboratory for Symbolic Cognitive Development, RIKEN Brain
|
||||
Science Insititute.
|
||||
</div>
|
||||
)}
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
||||
+72
-73
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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't exist.</p>
|
||||
<p>
|
||||
If the page does not automatically reload, please click <a href={url}>here</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>Page Not Found - {Functions.siteTitle(lang)}</title>
|
||||
</Helmet>
|
||||
<h1>Page Not Found</h1>
|
||||
<section>
|
||||
<p>The page you were trying to access doesn't exist.</p>
|
||||
<p>
|
||||
If the page does not automatically reload, please go to the <a href={url}>top page</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageNotFound;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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 & Laboratory for Symbolic Cognitive Development</footer>
|
||||
</div>
|
||||
</div>
|
||||
<footer>BSI-NI & Laboratory for Symbolic Cognitive Development</footer>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default SiteIndex;
|
||||
|
||||
+95
-108
@@ -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 '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '&':
|
||||
return '&';
|
||||
case "'":
|
||||
return ''';
|
||||
case '"':
|
||||
return '"';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
return str.replace(/(<|>|&|'|")/g, (match) => {
|
||||
switch (match) {
|
||||
case '<':
|
||||
return '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '&':
|
||||
return '&';
|
||||
case "'":
|
||||
return ''';
|
||||
case '"':
|
||||
return '"';
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user