first commit.

This commit is contained in:
Yoshihiro OKUMURA 2022-06-08 13:15:52 +09:00
commit 3ed26dd828
238 changed files with 16732 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.eslintcache
npm-debug.log*
yarn-debug.log*
yarn-error.log*

44
README.md Normal file
View File

@ -0,0 +1,44 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

65
package.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "brainatlas",
"version": "1.0.0",
"private": true,
"dependencies": {
"@orrisroot/react-html-parser": "^2.1.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.0",
"@types/async-lock": "^1.1.5",
"@types/jest": "^28.1.1",
"@types/lokijs": "^1.5.7",
"@types/node": "^17.0.41",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@types/react-router-dom": "^5.3.3",
"@types/react-router-hash-link": "^2.4.5",
"async-lock": "^1.3.1",
"axios": "^0.27.2",
"lokijs": "^1.5.12",
"moment": "^2.29.3",
"rc-tree": "^5.6.5",
"react": "^18.1.0",
"react-app-polyfill": "^3.0.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.1.0",
"react-ga4": "^1.4.1",
"react-helmet-async": "^1.3.0",
"react-image-lightbox": "^5.1.4",
"react-overlays": "^5.2.0",
"react-router-dom": "^6.3.0",
"react-router-hash-link": "^2.4.3",
"react-scripts": "^5.0.1",
"react-spinner-material": "^1.4.0",
"typescript": "^4.7.3",
"web-vitals": "^2.1.4",
"xregexp": "^5.1.1"
},
"resolutions": {
"@svgr/webpack": "^6.2.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all",
"ie 11"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version",
"ie 11"
]
}
}

17
public/.htaccess Normal file
View File

@ -0,0 +1,17 @@
RewriteEngine on
RewriteBase /
RewriteCond %{QUERY_STRING} (^|&)file_id=([0-9]+)($|&)
RewriteRule ^degu/modules/xoonips/download.php /degu/file/%2? [R=301,L]
RewriteCond %{QUERY_STRING} (^|&)file_id=([0-9]+)($|&)
RewriteRule ^jm/modules/xoonips/download.php /jm/file/%2? [R=301,L]
RewriteCond %{QUERY_STRING} (^|&)file_id=([0-9]+)($|&)
RewriteRule ^marmoset/modules/xoonips/download.php /marmoset/file/%2? [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_FILENAME}index.html !-f
RewriteRule . /index.html [L]

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

23
public/index.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="BSI-NI Brain Atlas - This site provides the 3D atlas and the standard MRI template of Degu, Common Marmoset and Japanese Macaque Monkey."
/>
<meta name="robots" content="index,follow" />
<meta name="keywords" content="neuroinformatics, bsi-ni, brain science, xoonips, database, brain atlas, degu, japanese macaque monkey, common marmoset" />
<meta name="author" content="BSI-NI and Laboratory for Symbolic Cognitive Development, RIKEN Brain Science Institute" />
<meta name="copyright" content="Copyright &copy; 2017" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>BSI-NI Brain Atlas</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

15
public/manifest.json Normal file
View File

@ -0,0 +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"
}

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

2
src/App.css Normal file
View File

@ -0,0 +1,2 @@
@import url('./common/assets/xoops.css');
@import url('./common/assets/theme/style.css');

9
src/App.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

51
src/App.tsx Normal file
View File

@ -0,0 +1,51 @@
import React, { useEffect, useState } 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 "./App.css";
import AppRoot from "./common/AppRoot";
import Config, { MultiLang } from "./config";
const AppMain: React.FC = () => {
const [cookies, setCookie] = useCookies();
const [lang, setLang] = useState<MultiLang>(["en", "ja"].includes(cookies.ml_lang) ? cookies.ml_lang : "en");
const location = useLocation();
useEffect(() => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") {
ReactGA.send("pageview");
}
const params = new URLSearchParams(location.search);
const ml_lang = params.get("ml_lang");
if (ml_lang != null && ["en", "ja"].includes(ml_lang)) {
if (cookies.ml_lang !== ml_lang) {
setCookie("ml_lang", ml_lang);
}
if (lang !== ml_lang) {
setLang(ml_lang as MultiLang);
}
}
window.scrollTo(0, 0);
}, [location, cookies, setCookie, lang, setLang]);
return <AppRoot lang={lang} />;
};
const App: React.FC = () => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") {
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID);
}
return (
<BrowserRouter>
<CookiesProvider>
<HelmetProvider>
<AppMain />
</HelmetProvider>
</CookiesProvider>
</BrowserRouter>
);
};
export default App;

63
src/common/AppRoot.tsx Normal file
View File

@ -0,0 +1,63 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Route, Routes, useLocation } from "react-router-dom";
import { BrainAtlasType, MultiLang } from "../config";
import SiteIndex from "../custom/SiteIndex";
import Functions from "../functions";
import Footer from "./Footer";
import Header from "./Header";
import PageNotFound from "./lib/PageNotFound";
import MainContent from "./MainContent";
interface Props {
lang: MultiLang;
}
interface PropsMain extends Props {
type: BrainAtlasType;
isHtml: boolean;
isLink: boolean;
}
const PageMain: React.FC<PropsMain> = (props: PropsMain) => {
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: 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;
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;

34
src/common/Footer.tsx Normal file
View File

@ -0,0 +1,34 @@
import React from "react";
import { Link } from "react-router-dom";
import { BrainAtlasType, MultiLang } from "../config";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
isLink: boolean;
}
const Footer: React.FC<Props> = (props: Props) => {
const { type, isLink } = props;
return (
<footer>
<div className="links clearfix">
<span className={`image ${type}`}>&nbsp;</span>
{!isLink && (
<Link className="relatedLink" to={"/" + type + "_html/link.html"}>
<span>Related Link</span>
</Link>
)}
<a className="bsi-ni" href="https://bsi-ni.brain.riken.jp/" target="_blank" rel="noopener noreferrer">
<span>BSI-Neuroinformatics</span>
</a>
<span className="gotoTop">
<Link to="/">&raquo; GO TO TOP</Link>
</span>
</div>
{type === "marmoset" ? <div className="copyright">Copyright (C) 2018 Laboratory for Symbolic Cognitive Development, RIKEN Center for Biosystems Dynamics Research.</div> : <div className="copyright">Copyright (C) 2017 BSI-NI Project &amp; Laboratory for Symbolic Cognitive Development, RIKEN Brain Science Insititute.</div>}
</footer>
);
};
export default Footer;

92
src/common/Header.tsx Normal file
View File

@ -0,0 +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";
interface Props {
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",
};
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",
};
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 (["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;
};
const Header: React.FC<Props> = (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 xtype = getHeaderType(type, pathname, search);
const title = Config.SITE_TITLE + " " + titles[xtype];
const url = urls[xtype];
return (
<header className={xtype}>
<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;

View File

@ -0,0 +1,38 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import { BrainAtlasType, MultiLang } from "../config";
import CoverPage from "../custom/CoverPage";
import RelatedLink from "../custom/RelatedLink";
import Database from "../database/Database";
import DatabaseTop from "../database/DatabaseTop";
import XoopsPathRedirect from "./XoopsPathRedirect";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
isHtml: boolean;
isLink: boolean;
}
const MainContent: React.FC<Props> = (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={<DatabaseTop lang={lang} type={type} />} />
<Route path="modules/xoonips/*" element={<Database lang={lang} type={type} />} />
<Route path="*" element={<XoopsPathRedirect lang={lang} />} />
</Routes>
)}
</div>
);
};
export default MainContent;

View File

@ -0,0 +1,29 @@
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import { MultiLang } from "../config";
import PageNotFound from "./lib/PageNotFound";
interface Props {
lang: MultiLang;
}
const XoopsPathRedirect: React.FC<Props> = (props: Props) => {
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 <Navigate to={url} />;
};
export default XoopsPathRedirect;

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

View File

@ -0,0 +1,38 @@
[
{
"title": "[en]Home[/en][ja]ホーム[/ja]",
"link": "/"
},
{
"title": "[en]About \"Dynamic Brain\"[/en][ja]\"Dynamic Brain\"とは[/ja]",
"link": "/documents/concept.html"
},
{
"title": "[en]Articles[/en][ja]特集記事[/ja]",
"link": "/mediawiki/"
},
{
"title": "[en]Academic Conferences[/en][ja]学術会議[/ja]",
"link": "/mediawiki/Academic_conferences"
},
{
"title": "[en]Collaborative Projects[/en][ja]連携プロジェクト[/ja]",
"link": "/mediawiki/Collaborative_Projects"
},
{
"title": "[en]Collaborative Hackathon[/en][ja]連携ハッカソン[/ja]",
"link": "/mediawiki/Hackathon_Page"
},
{
"title": "[en]Models[/en][ja]数理モデル[/ja]",
"link": "/database/search/itemtype/model"
},
{
"title": "[en]How to get \"PhysioDesigner\"[/en][ja]PhysioDesignerダウンロード[/ja]",
"link": "http://physiodesigner.org/download/"
},
{
"title": "[en]See more...[/en][ja]更に見る...[/ja]",
"link": "/database"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,302 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
#root,
#page {
height: 100%;
width: 100%;
}
body {
font-family: arial, sans-serif;
color: #666666;
background-color: #ffffff;
margin: 0;
font-size: 90%;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 0.5rem;
font-family: inherit;
font-weight: bold;
line-height: 1.2;
color: inherit;
}
h1 {
font-size: 1.802em;
background: url(images/indent_h1.png) no-repeat left center;
text-indent: 40px;
}
h2 {
font-size: 1.602em;
background: url(images/indent_h2.png) no-repeat left center;
text-indent: 30px;
}
h3 {
font-size: 1.424em;
background: url(images/indent_h3.png) no-repeat left center;
text-indent: 25px;
}
h4 {
font-size: 1.266em;
}
h5 {
font-size: 1.125em;
}
h6 {
font-size: 1em;
}
a {
color: #d76c6c;
text-decoration: none;
}
a:visited {
color: #b25757;
}
a:hover {
color: #f60;
text-decoration: underline;
}
table {
width: 100%;
}
td,
th {
text-align: left;
vertical-align: top;
}
th {
padding: 3px;
vertical-align: middle;
font-weight: normal;
color: #666;
border-bottom: 1px solid #ccc;
background: #eee;
}
tr.even td {
padding: 2px;
border-bottom: 1px solid #cacaca;
}
tr.odd td {
padding: 2px;
border-bottom: 1px solid #cacaca;
}
tr.even {
color: inherit;
background: #f7f7f7;
}
tr.odd {
color: inherit;
background: #fafafa;
}
tr.odd:hover,
tr.even:hover {
color: #000;
border-bottom: 1px solid #cacaca;
background: #dfdfdf;
}
.outer {
border: 1px solid #eee;
}
.head {
padding: 5px;
color: inherit;
border-bottom: 1px solid #cacaca;
background: #f2f2f2;
}
.even {
padding: 2px;
color: inherit;
border-bottom: 1px solid #cacaca;
background: #f7f7f7;
}
.odd {
padding: 2px;
color: inherit;
border-bottom: 1px solid #cacaca;
background: #fafafa;
}
.foot {
padding: 5px;
font-weight: bold;
}
.clearfix::after {
content: "";
display: block;
clear: both;
}
.hidden {
display: none;
}
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.font-weight-bold {
font-weight: bold;
}
.font-italic {
font-style: italic;
}
.block .blockTitle {
height: 21px;
margin-bottom: 0.4rem;
padding: 0 10px 0 32px;
color: #369;
line-height: 21px;
font-weight: bold;
}
.block .blockContent {
padding: 0 10px;
font-size: 13px;
}
header {
min-width: 960px;
padding-top: 26px;
}
header.degu {
background: url(images/header_degu_bar.png) repeat-x;
}
header.jm {
background: url(images/header_jm_bar.png) repeat-x;
}
header.marmoset {
background: url(images/header_marmoset_bar.png) repeat-x;
}
header.marmoset_3d {
background: url(images/header_marmoset_3d_bar.png) repeat-x;
}
header.marmoset_std {
background: url(images/header_marmoset_std_bar.png) repeat-x;
}
header .selLang {
text-align: right;
margin-right: 30px;
font-size: 80%;
}
header .title {
width: 960px;
height: 300px;
margin: 0 auto;
}
header .title span {
display: none;
}
header.degu .title {
background-image: url(images/header_degu.png);
}
header.jm .title {
background-image: url(images/header_jm.png);
}
header.marmoset .title {
background-image: url(images/header_marmoset.png);
}
header.marmoset_3d .title {
background-image: url(images/header_marmoset_3d.png);
}
header.marmoset_std .title {
background-image: url(images/header_marmoset_std.png);
}
footer {
min-width: 960px;
margin: 10px auto;
}
footer .links {
width: 960px;
margin: 0 auto;
}
footer .links > a {
display: inline-block;
vertical-align: middle;
}
footer .links .image {
display: inline-block;
margin: 0 20px;
width: 111px;
height: 131px;
vertical-align: middle;
}
footer .links .image.degu {
background-image: url(images/footer_degu.png);
}
footer .links .image.jm {
background-image: url(images/footer_jm.png);
}
footer .links .image.marmoset {
background-image: url(images/footer_marmoset.png);
}
footer .links .relatedLink {
margin: 0 30px;
height: 57px;
width: 168px;
background-image: url(images/related_link.png);
}
footer .links .bsi-ni {
margin: 0 30px;
width: 300px;
height: 65px;
background-image: url(images/footer_link_bsi-ni.png);
}
footer .links .relatedLink span,
footer .links .bsi-ni span {
display: none;
}
footer .links .gotoTop {
float: right;
margin: 50px 30px;
vertical-align: middle;
}
footer .links .gotoTop a {
text-align: right;
font-size: 12px;
font-weight: bold;
color: #62c2c2;
}
footer .copyright {
margin: 5px;
font-size: 10px;
text-align: center;
}
#main {
width: 960px;
margin: 0 auto;
line-height: 1.5;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
.mainContent {
margin: 20px 60px;
}

View File

@ -0,0 +1,17 @@
img {border: 0;}
#xoopsHiddenText {visibility: hidden; color: #000000; font-weight: normal; font-style: normal; text-decoration: none;}
.pagneutral {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/pagneutral.gif);}
.pagact {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/pagact.gif);}
.paginact {font-size: 10px; width: 16px; height: 19px;text-align: center; background-image: url(./images/paginact.gif);}
#mainmenu a {text-align:left; display: block; margin: 0; padding: 4px;}
#mainmenu a.menuTop {padding-left: 3px;}
#mainmenu a.menuMain {padding-left: 3px;}
#mainmenu a.menuSub {padding-left: 9px;}
#usermenu a {text-align:left; display: block; margin: 0; padding: 4px;}
#usermenu a.menuTop {}
#usermenu a.highlight {color: #0000ff; background-color: #fcc;}

View File

@ -0,0 +1,57 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
interface Props {
url: string;
title: string;
image: string;
imageHover?: string;
}
interface State {
image: string;
}
class LinkImage extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
image: props.image,
};
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseOut = this.handleMouseOut.bind(this);
}
handleMouseOver() {
const { imageHover } = this.props;
if (typeof imageHover !== "undefined") {
this.setState({ image: imageHover });
}
}
handleMouseOut() {
const { image: imageNormal, imageHover } = this.props;
if (typeof imageHover !== "undefined") {
this.setState({ image: imageNormal });
}
}
render() {
const { url, title } = this.props;
const image = <img style={{ verticalAlign: "middle" }} src={this.state.image} alt={title} title={title} />;
if (url.match(/^(\/|\.)/) === null) {
return (
<a href={url} target="_blank" rel="noopener noreferrer" onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
{image}
</a>
);
}
return (
<Link to={url} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
{image}
</Link>
);
}
}
export default LinkImage;

View File

@ -0,0 +1,12 @@
import React from "react";
import Spinner from "react-spinner-material";
const Loading: React.FC = () => {
return (
<div style={{ display: "flex", justifyContent: "center", alignContent: "center", margin: "100px 0" }}>
<Spinner radius={60} color={"#cccccc"} stroke={8} visible={true} />
</div>
);
};
export default Loading;

View File

@ -0,0 +1,15 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const NoticeSiteHasBeenArchived: React.FC<Props> = (props: Props) => {
const { lang } = props;
const notice = "[en]This site has been archived since FY2019 and is no longer updated.[/en][ja]このサイトは、2019年度よりアーカイブサイトとして運用されています。[/ja]";
return <p style={{ color: "red" }}>{Functions.mlang(notice, lang)}</p>;
};
export default NoticeSiteHasBeenArchived;

View File

@ -0,0 +1,50 @@
import React, { useEffect } from "react";
import { Helmet } from "react-helmet-async";
import { useLocation, useNavigate } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const PageNotFound: React.FC<Props> = (props) => {
const { lang } = props;
const location = useLocation();
const navigate = useNavigate();
const url = "/";
useEffect(() => {
let timer: NodeJS.Timeout | null = null;
if (location.pathname !== "/" && process.env.NODE_ENV === 'production') {
if (timer === null) {
timer = setTimeout(() => {
navigate(url);
}, 5000);
}
}
return () => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
};
}, [location, navigate]);
return (
<div>
<Helmet>
<title>Page Not Found - {Functions.siteTitle(lang)}</title>
</Helmet>
<h1>Page Not Found</h1>
<section>
<p>The page you were trying to access doesn't exist.</p>
<p>
If the page does not automatically reload, please click <a href={url}>here</a>
</p>
</section>
</div>
);
};
export default PageNotFound;

View File

@ -0,0 +1,44 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import imageRank2 from "../assets/images/rank3dbf8e94a6f72.gif";
import imageRank3 from "../assets/images/rank3dbf8e9e7d88d.gif";
import imageRank4 from "../assets/images/rank3dbf8ea81e642.gif";
import imageRank5 from "../assets/images/rank3dbf8eb1a72e7.gif";
import imageRank6 from "../assets/images/rank3dbf8edf15093.gif";
import imageRank7 from "../assets/images/rank3dbf8ee8681cd.gif";
import imageRank1 from "../assets/images/rank3e632f95e81ca.gif";
interface Props {
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 },
];
const UserRankStarImage: React.FC<Props> = (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} />;
};
export default UserRankStarImage;

View File

@ -0,0 +1,132 @@
import ReactHtmlParser, { convertNodeToElement, DomElement, DomNode, Transform } from "@orrisroot/react-html-parser";
import React from "react";
import { HashLink } from "react-router-hash-link";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
text: string;
dohtml?: boolean;
dosmiley?: boolean;
doxcode?: boolean;
doimage?: boolean;
dobr?: boolean;
}
const preConvertXCode = (text: string, doxcode: boolean): string => {
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (m0, m1) => {
return "[code]" + Functions.base64Encode(m1) + "[/code]";
});
}
return text;
};
const postConvertXCode = (text: string, doxcode: boolean, doimage: boolean): string => {
if (doxcode) {
return text.replace(/\[code\](.*)\[\/code\]/gs, (m0, m1) => {
const text = convertXCode(Functions.htmlspecialchars(Functions.base64Decode(m1)), doimage);
return '<div class="xoopsCode"><pre><code>' + text + "</code></pre></div>";
});
}
return text;
};
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;
};
const convertXCode = (text: string, doimage: boolean): string => {
// TODO: implement
return text;
};
const convertSmiley = (text: string) => {
// TODO: implement
return text;
};
const convertBr = (text: string): string => {
return text.replace(/(\r?\n|\r)/g, "<br />");
};
const cssConvert = (text: string): object => {
const ret: any = {};
text.split(";").forEach((line) => {
const line_ = line.trim();
if (line.length === 0) {
return;
}
const kv = line_.split(":");
const key = Functions.camelCase(kv[0].trim());
const value = kv[1].trim();
ret[key] = value;
});
return ret;
};
const xoopsTransform: Transform = (node: DomNode, index: number | string, transform?: Transform): React.ReactNode => {
if (node.type === "tag") {
const node_ = node as DomElement;
if (node_.name === "a") {
const url = node_.attribs?.["href"] || "/";
const download = (node_.attribs && node_.attribs["download"]) || "";
const rel = (node_.attribs && node_.attribs["rel"]) || "";
const klass = (node_.attribs && node_.attribs["class"]) || "";
const isFile = download !== "" || /\.(zip|pdf|png|gif|jpg)$/.test(url);
const isExternal = /external/.test(rel) || /external/.test(klass) || /^(mailto|https?:?\/\/)/.test(url);
if (!isFile && !isExternal) {
const style = node_.attribs?.["style"] || "";
const title = node_.attribs?.["title"];
return (
<HashLink key={index} to={url} style={cssConvert(style)} title={title}>
{node_.children.map((value: DomNode, index: number) => {
return convertNodeToElement(value, index, transform);
})}
</HashLink>
);
}
}
if (node_.name === "img") {
const src = (node_.attribs && node_.attribs["src"]) || "";
node_.attribs["src"] = src.replace("`XOOPS_URL`", process.env.PUBLIC_URL);
return convertNodeToElement(node_, index, transform);
}
}
};
const XoopsCode: React.FC<Props> = (props: Props) => {
const { lang } = props;
let text = props.text;
const dohtml = !!props.dohtml;
const dosmiley = !!props.dosmiley;
const doxcode = !!props.doxcode;
const doimage = !!props.doimage;
const dobr = !!props.dobr;
text = preConvertXCode(text, doxcode);
if (!dohtml) {
text = Functions.htmlspecialchars(text);
text = convertClickable(text);
}
if (dosmiley) {
text = convertSmiley(text);
}
if (doxcode) {
text = convertXCode(text, doimage);
}
if (dobr) {
text = convertBr(text);
}
text = postConvertXCode(text, doxcode, doimage);
text = Functions.mlang(text, lang);
return <div>{ReactHtmlParser(text, { transform: xoopsTransform })}</div>;
};
export default XoopsCode;

18
src/config.ts Normal file
View File

@ -0,0 +1,18 @@
const SITE_TITLE = "BSI-NI Brain Atlas";
const SITE_SLOGAN = "Welcome to BRAIN ATLAS";
const GOOGLE_ANALYTICS_TRACKING_ID = "";
const XOONIPS_ITEMTYPES = ["files", "url", "paper", "book"];
export type MultiLang = "en" | "ja";
export type BrainAtlasType = "degu" | "jm" | "marmoset";
export const BrainAtlasTypes: ReadonlyArray<BrainAtlasType> = ["degu", "jm", "marmoset"];
const Config = {
SITE_TITLE,
SITE_SLOGAN,
GOOGLE_ANALYTICS_TRACKING_ID,
XOONIPS_ITEMTYPES,
};
export default Config;

View File

@ -0,0 +1,17 @@
.coverPage {
margin: 20px;
}
.coverPage .links {
list-style-type: square;
}
.coverPage .links li {
line-height: 2;
}
.coverPage .links li a {
font-size: 1.424em;
font-weight: bold;
color: #378893;
}
.coverPage .links li a:hover {
color: #62c2c2;
}

34
src/custom/CoverPage.tsx Normal file
View File

@ -0,0 +1,34 @@
import React from "react";
import { Link } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { BrainAtlasType, MultiLang } from "../config";
import styles from "./CoverPage.module.css";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
}
const CoverPage: React.FC<Props> = (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>
);
};
export default CoverPage;

View File

@ -0,0 +1,21 @@
.relatedLink {
margin: 20px;
}
.relatedLink .title {
height: 57px;
width: 168px;
background-image: url(../common/assets/theme/images/related_link.png);
}
.relatedLink .title span {
display: none;
}
.relatedLink .links li {
line-height: 2;
}
.relatedLink .links li a {
font-size: 95%;
color: #333333;
}
.relatedLink .links li a:hover {
color: #62c2c2;
}

View File

@ -0,0 +1,42 @@
import React from "react";
import { BrainAtlasType, MultiLang } from "../config";
import styles from "./RelatedLink.module.css";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
}
const RelatedLink: React.FC<Props> = (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 Congnition", 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;

View File

@ -0,0 +1,65 @@
.siteIndex {
min-width: 700px;
height: 100%;
width: 100%;
background-color: #9acae4;
}
.siteIndex header {
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);
}
.siteIndex .title span {
display: none;
}
.siteIndex .notice {
text-align: center;
height: 50px;
}
.siteIndex .image {
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;
}
.siteIndex .degu a {
background: url(assets/images/index_link_degu.png) no-repeat;
}
.siteIndex .jm a {
background: url(assets/images/index_link_jm.png) no-repeat;
}
.siteIndex .marmoset a {
background: url(assets/images/index_link_marmoset.png) no-repeat;
}
.siteIndex .links ul li span {
display: none;
}
.siteIndex footer {
color: yellow;
font-weight: 500;
text-align: center;
}

49
src/custom/SiteIndex.tsx Normal file
View File

@ -0,0 +1,49 @@
import React from "react";
import { Link } from "react-router-dom";
import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived";
import { MultiLang } from "../config";
import styles from "./SiteIndex.module.css";
interface Props {
lang: MultiLang;
}
const SiteIndex: React.FC<Props> = (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>
</div>
</div>
<footer>BSI-NI &amp; Laboratory for Symbolic Cognitive Development</footer>
</div>
);
};
export default SiteIndex;

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,70 @@
.database {
margin: 0;
}
.database :global(.list) {
width: 100%;
}
.database :global(.listTable) {
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;
}
.database :global(.itemDetail) {
border-collapse: separate;
border-spacing: 1px;
}
.database :global(.itemDetail .head) {
width: 30%;
}
.database :global(.itemDetail .readme),
.database :global(.itemDetail .rights) {
background-color: #ffffff;
width: 100%;
max-height: 200px;
overflow: auto;
}
.database :global(.advancedSearch .head) {
width: 30%;
}
.database :global(.advancedSearch .search) {
text-align: center;
margin: 10px;
}
.database :global(.advancedSearch .itemtype) {
margin-bottom: 5px;
}
.database :global(.advancedSearch .itemtype .itemtypeName),
.database :global(.advancedSearch .itemtype .itemtypeFields) {
border-collapse: separate;
border-spacing: 1px;
}
.database :global(.advancedSearch .itemtype .itemtypeName th) {
padding: 5px;
}
.database :global(.advancedSearch .fieldDateLabel) {
display: inline-block;
width: 50px;
}
.database :global(.advancedSearch .fieldDate select),
.database :global(.advancedSearch .fieldDate input) {
margin: 0 5px 0 0;
}

92
src/database/Database.tsx Normal file
View File

@ -0,0 +1,92 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Route, Routes, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { BrainAtlasType, MultiLang } from "../config";
import Functions from "../functions";
import styles from "./Database.module.css";
import DatabaseAdvancedSearch from "./DatabaseAdvancedSearch";
import DatabaseDetailItem from "./DatabaseDetailItem";
import DatabaseSearchByAdvancedKeyword from "./DatabaseSearchByAdvancedKeyword";
import DatabaseSearchByIndexId from "./DatabaseSearchByIndexId";
import DatabaseSearchByItemType from "./DatabaseSearchByItemType";
import DatabaseSearchByKeyword from "./DatabaseSearchByKeyword";
import DatabaseTop from "./DatabaseTop";
import { INDEX_ID_PUBLIC } from "./lib/IndexUtil";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
}
const ItemDetail: React.FC<Props> = (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 <DatabaseDetailItem lang={lang} id={itemId} doi={doi} type={type} />;
};
const ItemList: React.FC<Props> = (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 <DatabaseSearchByIndexId lang={lang} type={type} indexId={indexId} />;
};
const ItemSelect: React.FC<Props> = (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 <DatabaseSearchByItemType 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 <DatabaseSearchByItemType lang={lang} itemType={itemType} subItemType={subItemtype} type={type} />;
}
case "quicksearch": {
return <DatabaseSearchByKeyword lang={lang} type={type} />;
}
case "advanced": {
return <DatabaseSearchByAdvancedKeyword lang={lang} type={type} />;
}
}
return <PageNotFound lang={lang} />;
};
const Database: React.FC<Props> = (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={<DatabaseTop lang={lang} type={type} />} />
<Route path="index.php" element={<DatabaseTop 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={<DatabaseAdvancedSearch lang={lang} type={type} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</div>
);
};
export default Database;

View File

@ -0,0 +1,63 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import Config, { BrainAtlasType, MultiLang } from "../config";
import Functions from "../functions";
import ItemType from "./item-type";
import AdvancedSearchQuery from "./lib/AdvancedSearchQuery";
import ItemUtil from "./lib/ItemUtil";
interface PropsFC {
lang: MultiLang;
type: BrainAtlasType;
}
interface Props extends PropsFC {
navigate: any;
}
class DatabaseAdvancedSearch extends React.Component<Props> {
private query: AdvancedSearchQuery = new AdvancedSearchQuery();
constructor(props: Props) {
super(props);
this.handleClickSearchButton = this.handleClickSearchButton.bind(this);
}
handleClickSearchButton() {
const { type, navigate } = this.props;
if (!this.query.empty()) {
const url = ItemUtil.getSearchByAdvancedKeywordsUrl(type, this.query);
navigate(url);
}
}
render() {
const { lang } = this.props;
return (
<div className="advancedSearch">
<h3>{Functions.mlang("[en]Search Items[/en][ja]アイテム検索[/ja]", lang)}</h3>
<div className="search">
<button className="formButton" onClick={this.handleClickSearchButton}>
Search
</button>
</div>
{Config.XOONIPS_ITEMTYPES.map((type) => {
return <ItemType.AdvancedSearch key={type} type={"xnp" + type} lang={lang} query={this.query} />;
})}
<div className="search">
<button className="formButton" onClick={this.handleClickSearchButton}>
Search
</button>
</div>
</div>
);
}
}
const DatabaseAdvancedSearchFC: React.FC<PropsFC> = (props: PropsFC) => {
const { lang, type } = props;
return <DatabaseAdvancedSearch lang={lang} type={type} navigate={useNavigate()} />;
};
export default DatabaseAdvancedSearchFC;

View File

@ -0,0 +1,78 @@
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 Functions from "../functions";
import ItemType from "./item-type";
import ItemUtil, { Item } from "./lib/ItemUtil";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
id: number;
doi: string;
}
interface State {
loading: boolean;
item: Item | null;
}
class DatabaseDetailItem extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
loading: true,
item: null,
};
}
componentDidMount() {
this.updateItem();
}
componentDidUpdate(prevProps: Props, prevState: State) {
const { id, doi } = this.props;
if (id !== prevProps.id || doi !== prevProps.doi) {
this.updateItem();
}
}
updateItem() {
const { id, doi, type } = this.props;
if (doi !== "") {
ItemUtil.getByDoi(type, doi, (item) => {
this.setState({ loading: false, item });
});
} else if (id !== 0) {
ItemUtil.get(type, id, (item) => {
this.setState({ loading: false, item });
});
}
}
render() {
const { lang, type } = this.props;
if (this.state.loading) {
return <Loading />;
}
if (this.state.item === null) {
return <PageNotFound lang={lang} />;
}
return (
<>
<Helmet>
<title>
{Functions.mlang(this.state.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={this.state.item} type={type} />
</>
);
}
}
export default DatabaseDetailItem;

View File

@ -0,0 +1,67 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { BrainAtlasType, MultiLang } from "../config";
import AdvancedSearchQuery from "./lib/AdvancedSearchQuery";
import DatabaseListItem from "./lib/DatabaseListItem";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
interface PropsFC {
lang: MultiLang;
type: BrainAtlasType;
}
interface Props extends PropsFC {
location: any;
}
interface State {
search: string;
query: AdvancedSearchQuery;
}
class DatabaseSearchByAdvancedKeyword extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const search = props.location.search;
const query = ItemUtil.getAdvancedSearchQueryByQuery(search);
this.state = { search, query };
this.searchFunc = this.searchFunc.bind(this);
}
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const search = nextProps.location.search;
if (prevState.search !== search) {
const query = ItemUtil.getAdvancedSearchQueryByQuery(search);
return { search, query };
}
return null;
}
getUrl() {
const { type } = this.props;
return ItemUtil.getSearchByAdvancedKeywordsUrl(type, this.state.query);
}
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
const { type } = this.props;
ItemUtil.getListByAdvancedSearchQuery(type, this.state.query, condition, func);
}
render() {
const { lang, type } = this.props;
const baseUrl = this.getUrl();
return (
<div className="list">
<h3>Listing item</h3>
<DatabaseListItem lang={lang} url={baseUrl} search={this.searchFunc} type={type} />
</div>
);
}
}
const DatabaseSearchByAdvancedKeywordFC: React.FC<PropsFC> = (props: PropsFC) => {
const { lang, type } = props;
return <DatabaseSearchByAdvancedKeyword lang={lang} type={type} location={useLocation()} />;
};
export default DatabaseSearchByAdvancedKeywordFC;

View File

@ -0,0 +1,80 @@
import React, { Fragment } from "react";
import { Helmet } from "react-helmet-async";
import { Link } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { BrainAtlasType, MultiLang } from "../config";
import Functions from "../functions";
import DatabaseListIndex from "./lib/DatabaseListIndex";
import DatabaseListItem from "./lib/DatabaseListItem";
import IndexUtil, { Index } from "./lib/IndexUtil";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
export interface Props {
lang: MultiLang;
type: BrainAtlasType;
indexId: number;
}
class DatabaseSearchByIndexId extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.searchFunc = this.searchFunc.bind(this);
}
getUrl() {
const { type, indexId } = this.props;
if (indexId === 0) {
return "/";
}
return IndexUtil.getUrl(type, indexId);
}
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
const { type, indexId } = this.props;
if (indexId === 0) {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByIndexId(type, indexId, condition, func);
}
}
render() {
const { lang, type, indexId } = this.props;
const index = IndexUtil.get(type, indexId);
if (index === null) {
return <PageNotFound lang={lang} />;
}
const baseUrl = this.getUrl();
const pIndexes = IndexUtil.getParents(type, indexId);
const parents = pIndexes.map((value: Index) => {
const url: string = IndexUtil.getUrl(type, value.id);
const title = Functions.mlang(value.title, lang);
return (
<Fragment key={value.id}>
/ <Link to={url}>{title}</Link>{" "}
</Fragment>
);
});
const title = pIndexes
.map((value) => {
return "/" + Functions.mlang(value.title, lang);
})
.join("");
return (
<div className="list">
<Helmet>
<title>
{Functions.mlang(title, lang)} - {Functions.mlang("[en]Database[/en][ja]データベース[/ja]", lang)} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{Functions.mlang("[en]Listing item[/en][ja]アイテム一覧[/ja]", lang)}</h3>
<div>{parents}</div>
<DatabaseListIndex lang={lang} index={index} type={type} />
<DatabaseListItem lang={lang} url={baseUrl} search={this.searchFunc} type={type} />
</div>
);
}
}
export default DatabaseSearchByIndexId;

View File

@ -0,0 +1,48 @@
import React from "react";
import { BrainAtlasType, MultiLang } from "../config";
import Functions from "../functions";
import DatabaseListItem from "./lib/DatabaseListItem";
import ItemUtil, { SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
interface Props {
lang: MultiLang;
itemType: string;
subItemType: string;
type: BrainAtlasType;
}
class DatabaseSearchByItemType extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.searchFunc = this.searchFunc.bind(this);
}
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
const { itemType, subItemType, type } = this.props;
if (itemType === "") {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByItemType(type, itemType, subItemType, condition, func);
}
}
getUrl() {
const { itemType, subItemType, type } = this.props;
let url = ItemUtil.getItemTypeSearchUrl(type, itemType, subItemType);
return url;
}
render() {
const { lang, type } = this.props;
const baseUrl = this.getUrl();
return (
<div className="list">
<h3>{Functions.mlang("[en]Listing item[/en][ja]アイテム一覧[/ja]", lang)}</h3>
<DatabaseListItem lang={lang} url={baseUrl} search={this.searchFunc} type={type} />
</div>
);
}
}
export default DatabaseSearchByItemType;

View File

@ -0,0 +1,74 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { BrainAtlasType, MultiLang } from "../config";
import Functions from "../functions";
import DatabaseListItem from "./lib/DatabaseListItem";
import ItemUtil, { KeywordSearchType, SearchCallbackFunc, SortCondition } from "./lib/ItemUtil";
interface PropsFC {
lang: MultiLang;
type: BrainAtlasType;
}
interface Props extends PropsFC {
location: any;
}
interface State {
type: KeywordSearchType;
keyword: string;
}
class DatabaseSearchByKeyword extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const { location } = props;
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(location.search);
this.state = { type, keyword };
this.searchFunc = this.searchFunc.bind(this);
}
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(nextProps.location.search);
if (prevState.type !== type || prevState.keyword !== keyword) {
return { type, keyword };
}
return null;
}
getUrl() {
const { type } = this.props;
return ItemUtil.getSearchByKeywordUrl(type, this.state.type, this.state.keyword);
}
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
const { type } = this.props;
if (this.state.keyword === "") {
const res = { total: 0, data: [] };
func(res);
} else {
ItemUtil.getListByKeyword(type, this.state.type, this.state.keyword, condition, func);
}
}
render() {
const { lang, type } = this.props;
const baseUrl = this.getUrl();
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)} : {this.state.keyword}
</p>
<DatabaseListItem lang={lang} url={baseUrl} search={this.searchFunc} type={type} />
</div>
);
}
}
const DatabaseSearchByKeywordFC: React.FC<PropsFC> = (props: PropsFC) => {
const { lang, type } = props;
return <DatabaseSearchByKeyword lang={lang} type={type} location={useLocation()} />;
};
export default DatabaseSearchByKeywordFC;

View File

@ -0,0 +1,13 @@
.itemTypes .itemType {
width: 50%;
padding: 5px;
}
.itemTypes .itemType :global(table) {
width: auto;
}
.itemTypes .itemType :global(table .itemTypeName) {
vertical-align: middle;
font-size: large;
}

View File

@ -0,0 +1,41 @@
import React from "react";
import Config, { BrainAtlasType, MultiLang } from "../config";
import styles from "./DatabaseTop.module.css";
import ItemType from "./item-type";
interface Props {
lang: MultiLang;
type: BrainAtlasType;
}
const DatabaseTop: React.FC<Props> = (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>
);
};
export default DatabaseTop;

View File

@ -0,0 +1,148 @@
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
interface Props {
lang: MultiLang;
}
const DatabaseXoopsPathRedirect: React.FC<Props> = (props: Props) => {
const { lang } = props;
const getRedirectUrl = (): string => {
const location = useLocation();
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 itemId = query.get("item_id");
if (itemId !== null && itemId.match(/^\d+$/) !== null) {
return "/database/item/" + Functions.escape(itemId);
}
return "";
}
case "listitem.php": {
const indexId = query.get("index_id");
if (indexId !== null && indexId.match(/^\d+$/) !== null) {
const params = new URLSearchParams();
const map: any = {
orderby: { key: "orderby", isNumber: false },
order_dir: { key: "order_dir", isNumber: true },
itemcount: { key: "itemcount", isNumber: true },
page: { key: "page", isNumber: true },
};
for (let k in map) {
const v = query.get(k);
if (v === null || v.length === 0) {
continue;
}
if (map[k].isNumber && v.match(/^\d+$/) !== null) {
continue;
}
params.set(map[k].key, 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 });
const map: any = {
orderby: { key: "orderby", isNumber: false },
orderdir: { key: "order_dir", isNumber: true },
item_per_page: { key: "itemcount", isNumber: true },
page: { key: "page", isNumber: true },
};
for (let k in map) {
const v = query.get(k);
if (v === null || v.length === 0) {
continue;
}
if (map[k].isNumber && v.match(/^\d+$/) !== null) {
continue;
}
params.set(map[k].key, v);
}
return "/database/search?" + params.toString();
}
case "itemtypesearch": {
const itemType = query.get("search_itemtype");
if (itemType === null || 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} />;
};
export default DatabaseXoopsPathRedirect;

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

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