first commit.

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

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