first commit.
2
src/App.css
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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}`}> </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;
|
||||
92
src/common/Header.tsx
Normal 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;
|
||||
38
src/common/MainContent.tsx
Normal 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;
|
||||
29
src/common/XoopsPathRedirect.tsx
Normal 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;
|
||||
BIN
src/common/assets/images/mlang_english.gif
Normal file
|
After Width: | Height: | Size: 175 B |
BIN
src/common/assets/images/mlang_japanese.gif
Normal file
|
After Width: | Height: | Size: 126 B |
BIN
src/common/assets/images/no_avatar.gif
Normal file
|
After Width: | Height: | Size: 985 B |
BIN
src/common/assets/images/pagact.gif
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
src/common/assets/images/paginact.gif
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
src/common/assets/images/pagneutral.gif
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
src/common/assets/images/rank3dbf8e94a6f72.gif
Normal file
|
After Width: | Height: | Size: 905 B |
BIN
src/common/assets/images/rank3dbf8e9e7d88d.gif
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/common/assets/images/rank3dbf8ea81e642.gif
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
src/common/assets/images/rank3dbf8eb1a72e7.gif
Normal file
|
After Width: | Height: | Size: 856 B |
BIN
src/common/assets/images/rank3dbf8edf15093.gif
Normal file
|
After Width: | Height: | Size: 789 B |
BIN
src/common/assets/images/rank3dbf8ee8681cd.gif
Normal file
|
After Width: | Height: | Size: 779 B |
BIN
src/common/assets/images/rank3e632f95e81ca.gif
Normal file
|
After Width: | Height: | Size: 475 B |
BIN
src/common/assets/images/smil3dbd4bf386b36.gif
Normal file
|
After Width: | Height: | Size: 210 B |
BIN
src/common/assets/images/smil3dbd4d4e4c4f2.gif
Normal file
|
After Width: | Height: | Size: 204 B |
BIN
src/common/assets/images/smil3dbd4d6422f04.gif
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
src/common/assets/images/smil3dbd4d75edb5e.gif
Normal file
|
After Width: | Height: | Size: 204 B |
BIN
src/common/assets/images/smil3dbd4d8676346.gif
Normal file
|
After Width: | Height: | Size: 176 B |
BIN
src/common/assets/images/smil3dbd4d99c6eaa.gif
Normal file
|
After Width: | Height: | Size: 207 B |
BIN
src/common/assets/images/smil3dbd4daabd491.gif
Normal file
|
After Width: | Height: | Size: 170 B |
BIN
src/common/assets/images/smil3dbd4dbc14f3f.gif
Normal file
|
After Width: | Height: | Size: 210 B |
BIN
src/common/assets/images/smil3dbd4dcd7b9f4.gif
Normal file
|
After Width: | Height: | Size: 278 B |
BIN
src/common/assets/images/smil3dbd4ddd6835f.gif
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
src/common/assets/images/smil3dbd4df1944ee.gif
Normal file
|
After Width: | Height: | Size: 678 B |
BIN
src/common/assets/images/smil3dbd4e02c5440.gif
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
src/common/assets/images/smil3dbd4e1748cc9.gif
Normal file
|
After Width: | Height: | Size: 209 B |
BIN
src/common/assets/images/smil3dbd4e29bbcc7.gif
Normal file
|
After Width: | Height: | Size: 492 B |
BIN
src/common/assets/images/smil3dbd4e398ff7b.gif
Normal file
|
After Width: | Height: | Size: 205 B |
BIN
src/common/assets/images/smil3dbd4e4c2e742.gif
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/common/assets/images/smil3dbd4e5e7563a.gif
Normal file
|
After Width: | Height: | Size: 736 B |
BIN
src/common/assets/images/smil3dbd4e7853679.gif
Normal file
|
After Width: | Height: | Size: 273 B |
38
src/common/assets/main-menu.json
Normal 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"
|
||||
}
|
||||
]
|
||||
BIN
src/common/assets/theme/images/footer_degu.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/common/assets/theme/images/footer_jm.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
src/common/assets/theme/images/footer_link_bsi-ni.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/common/assets/theme/images/footer_marmoset.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/common/assets/theme/images/header_degu.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
src/common/assets/theme/images/header_degu_bar.png
Normal file
|
After Width: | Height: | Size: 124 B |
BIN
src/common/assets/theme/images/header_jm.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
src/common/assets/theme/images/header_jm_bar.png
Normal file
|
After Width: | Height: | Size: 124 B |
BIN
src/common/assets/theme/images/header_marmoset.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
src/common/assets/theme/images/header_marmoset_3d.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
src/common/assets/theme/images/header_marmoset_3d_bar.png
Normal file
|
After Width: | Height: | Size: 124 B |
BIN
src/common/assets/theme/images/header_marmoset_bar.png
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
src/common/assets/theme/images/header_marmoset_std.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/common/assets/theme/images/header_marmoset_std_bar.png
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
src/common/assets/theme/images/indent.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
src/common/assets/theme/images/indent_h1.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/common/assets/theme/images/indent_h2.png
Normal file
|
After Width: | Height: | Size: 808 B |
BIN
src/common/assets/theme/images/indent_h3.png
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
src/common/assets/theme/images/indent_hover.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
src/common/assets/theme/images/related_link.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
302
src/common/assets/theme/style.css
Normal 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;
|
||||
}
|
||||
17
src/common/assets/xoops.css
Normal 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;}
|
||||
57
src/common/lib/LinkImage.tsx
Normal 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;
|
||||
12
src/common/lib/Loading.tsx
Normal 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;
|
||||
15
src/common/lib/NoticeSiteHasBeenArchived.tsx
Normal 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;
|
||||
50
src/common/lib/PageNotFound.tsx
Normal 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;
|
||||
44
src/common/lib/UserRankStarImage.tsx
Normal 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;
|
||||
132
src/common/lib/XoopsCode.tsx
Normal 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
@@ -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;
|
||||
17
src/custom/CoverPage.module.css
Normal 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
@@ -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;
|
||||
21
src/custom/RelatedLink.module.css
Normal 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;
|
||||
}
|
||||
42
src/custom/RelatedLink.tsx
Normal 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;
|
||||
65
src/custom/SiteIndex.module.css
Normal 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
@@ -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 & Laboratory for Symbolic Cognitive Development</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SiteIndex;
|
||||
BIN
src/custom/assets/images/index_image.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
src/custom/assets/images/index_link_degu.png
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
src/custom/assets/images/index_link_jm.png
Normal file
|
After Width: | Height: | Size: 978 B |
BIN
src/custom/assets/images/index_link_marmoset.png
Normal file
|
After Width: | Height: | Size: 745 B |
BIN
src/custom/assets/images/index_title.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
70
src/database/Database.module.css
Normal 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
@@ -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;
|
||||
63
src/database/DatabaseAdvancedSearch.tsx
Normal 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;
|
||||
78
src/database/DatabaseDetailItem.tsx
Normal 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;
|
||||
67
src/database/DatabaseSearchByAdvancedKeyword.tsx
Normal 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;
|
||||
80
src/database/DatabaseSearchByIndexId.tsx
Normal 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;
|
||||
48
src/database/DatabaseSearchByItemType.tsx
Normal 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;
|
||||
74
src/database/DatabaseSearchByKeyword.tsx
Normal 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;
|
||||
13
src/database/DatabaseTop.module.css
Normal 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;
|
||||
}
|
||||
41
src/database/DatabaseTop.tsx
Normal 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;
|
||||
148
src/database/DatabaseXoopsPathRedirect.tsx
Normal 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;
|
||||
BIN
src/database/assets/images/icon_binder.gif
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
src/database/assets/images/icon_book.gif
Normal file
|
After Width: | Height: | Size: 375 B |
BIN
src/database/assets/images/icon_conference.gif
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
src/database/assets/images/icon_data.gif
Normal file
|
After Width: | Height: | Size: 402 B |
BIN
src/database/assets/images/icon_files.gif
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
src/database/assets/images/icon_folder.gif
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/database/assets/images/icon_memo.gif
Normal file
|
After Width: | Height: | Size: 166 B |
BIN
src/database/assets/images/icon_model.gif
Normal file
|
After Width: | Height: | Size: 409 B |
BIN
src/database/assets/images/icon_paper.gif
Normal file
|
After Width: | Height: | Size: 479 B |