first commit.

This commit is contained in:
2022-06-20 17:51:03 +09:00
commit 03456410f8
297 changed files with 26335 additions and 0 deletions

2
src/App.css Normal file
View File

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

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

@@ -0,0 +1,10 @@
import { render, screen } from "@testing-library/react";
import React from "react";
import App from "./App";
test("renders welcome message", () => {
window.scrollTo = () => {};
render(<App />);
const linkElement = screen.getByText(/NeuroImaging Platform/);
expect(linkElement).toBeInTheDocument();
});

20
src/App.tsx Normal file
View File

@@ -0,0 +1,20 @@
import React from "react";
import { CookiesProvider } from "react-cookie";
import { HelmetProvider } from "react-helmet-async";
import { BrowserRouter } from "react-router-dom";
import "./App.css";
import AppRoot from "./common/AppRoot";
const App: React.FC = () => {
return (
<BrowserRouter>
<CookiesProvider>
<HelmetProvider>
<AppRoot />
</HelmetProvider>
</CookiesProvider>
</BrowserRouter>
);
};
export default App;

View File

@@ -0,0 +1,31 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Route, Routes } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import BrainExplorerIndex from "./BrainExplorerIndex";
interface Props {
lang: MultiLang;
}
const BrainExplorer: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<Helmet>
<title>Brain Explorer - {Functions.siteTitle(lang)}</title>
</Helmet>
<Routes>
<Route path="index_jm_eng.php" element={<BrainExplorerIndex lang="en" type="jm" />} />
<Route path="index_jm_jpn.php" element={<BrainExplorerIndex lang="ja" type="jm" />} />
<Route path="index_hu_eng.php" element={<BrainExplorerIndex lang="en" type="hu" />} />
<Route path="index_hu_jpn.php" element={<BrainExplorerIndex lang="ja" type="hu" />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
};
export default BrainExplorer;

View File

@@ -0,0 +1,49 @@
.panel {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: white;
z-index: 1;
}
.panel::after {
content: "";
display: block;
clear: both;
}
.panelNavi {
width: 22%;
height: 100%;
border-right: 2px solid #cccccc;
float: left;
}
.panelButtons {
border-bottom: 2px solid #cccccc;
padding: 5px 10px;
}
.panelMenu {
padding: 5px 10px;
}
.panelMenuAistLink {
text-align: center;
}
.panelContent {
height: 100%;
overflow: auto;
float: right;
padding: 5px;
width: calc(100% - 22% - 2px);
}
.panelContent iframe {
width: 100%;
height: 100%;
border: 0;
}

View File

@@ -0,0 +1,111 @@
import React, { useState } from "react";
import { Modal } from "react-overlays";
import { Link, useLocation } from "react-router-dom";
import { MultiLang } from "../config";
import Functions from "../functions";
import imageLogoAist from "./assets/images/logo_aist.png";
import imageNaviHome from "./assets/images/navi_home.png";
import imageNaviInfo from "./assets/images/navi_info.png";
import styles from "./BrainExplorerIndex.module.css";
interface Props {
lang: MultiLang;
type: "jm" | "hu";
}
const title = {
en: "Brain Atlas Database of Japanese Monkey for WWW.",
ja: "脳画像データベース",
};
const description = {
en: "Neuroscience researches for elucidation of brain functions require electrophysiological, pharmacological or anatomical operations into the brain. However, no brain atlas of a Japanese monkey, which is used widely in these experiments, was made except detail maps of the brain stem. We have made magnetic resonance images as well as histological sections of a Japanese monkey brain. For the purpose of the practical and efficient use of these images, we will construct computer database for WWW, in which we can display, magnify, and compare these images by easy mouse operations.",
ja: "脳の優れた機能を解明し工学に利用するために、ヒトに近い機能を持った霊長類の神経科学的研究が行われている。脳機能の解明のためには脳の内部に対してさまざまな処置を施す必要がある。例えば、脳の特定の位置に記録、刺激電極を刺入する、薬剤を注入する、局部的に破壊するなどの場合がある。これらの処置に必要な資料は、脳の組織標本である。しかし、研究で良く使われるニホンザルの脳の組織標本は脳幹部を除いては今まで利用できる資料はなかった。そこで脳のMRI磁気共鳴映像法連続画像を撮ると共に、高次機能を調べるうえで重要な脳皮質全体を含む組織標本の連続切片を作成した。実験上また文献を読むときなどの資料として有効かつ簡便に活用するために、それらの画像をデジタル化し、マウスの操作のみで簡便に参照できるデータベースを構築し、WWW上で公開する。",
};
const BrainExplorerIndex: React.FC<Props> = (props: Props) => {
const { lang, type } = props;
const [showInfo, setShowInfo] = useState(false);
const location = useLocation();
const parentUrl = location.pathname.substring(0, location.pathname.lastIndexOf("/"));
const getUrl = (type: string, lang: MultiLang) => {
const label = { en: "eng", ja: "jpn" };
return `${parentUrl}/index_${type}_${label[lang]}.php`;
};
const renderShowInfo = () => {
return (
<div>
<h1>{title[lang]}</h1>
<hr />
<p>{description[lang]}</p>
<hr />
</div>
);
};
const renderBrainExplorerContent = () => {
const url = `./${type === "hu" ? "human" : "jmonkey"}.html?LANG=${lang === "en" ? "ENG" : "JPN"}`;
return <iframe src={url} title="image"></iframe>;
};
return (
<Modal show={true}>
<div className={styles.panel}>
<div className={styles.panelNavi}>
<div className={styles.panelButtons}>
<Link to="/">
<img src={imageNaviHome} alt="HOME" />
</Link>
&nbsp;
<a
href="/"
onClick={(e) => {
e.preventDefault();
setShowInfo(true);
}}
>
<img src={imageNaviInfo} alt="INFO" />
</a>
</div>
<div className={styles.panelMenu}>
<h3>Brain Explorer [MRI]</h3>
<ul>
<li>
<Link to={getUrl("jm", lang)} onClick={() => setShowInfo(false)}>
{Functions.mlang("[en]Japanese Monkey[/en][ja]ニホンザル[/ja]", lang)}
<br />
<em>(Macaca fuscata)</em>
<br />
{Functions.mlang("[en]Developmental Data[/en][ja]発達の様子[/ja]", lang)}
</Link>
</li>
<li>
<Link to={getUrl("hu", lang)} onClick={() => setShowInfo(false)}>
{Functions.mlang("[en]Human[/en][ja]ヒト[/ja]", lang)}
<br />
<em>(Homo sapiens)</em>
</Link>
</li>
</ul>
<hr />
<ul>
<li>
<Link to={getUrl(type, lang === "en" ? "ja" : "en")}>{Functions.mlang("[en]Japanese page.[/en][ja]English page.[/ja]", lang)}</Link>
</li>
</ul>
<hr />
<div className={styles.panelMenuAistLink}>
<a href="http://www.aist.go.jp/" target="_blank" rel="noopener noreferrer">
<img src={imageLogoAist} alt="AIST" />
</a>
</div>
</div>
</div>
<div className={styles.panelContent}>{showInfo ? renderShowInfo() : renderBrainExplorerContent()}</div>
</div>
</Modal>
);
};
export default BrainExplorerIndex;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

1035
src/brainexplorer/lib/g3d.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,616 @@
export interface M3DHeader {
magic: number;
x: number;
y: number;
z: number;
filename: string;
subject: string;
direction: string;
date: number;
fov: number;
weight: number;
ax: number;
ay: number;
az: number;
ox: number;
oy: number;
oz: number;
dlen: number;
dth: number;
memo: string;
}
interface M3DImageRenderingContext {
a1: number;
a2: number;
a3: number;
b1: number;
b2: number;
b3: number;
c1: number;
c2: number;
c3: number;
d1: number;
d2: number;
d3: number;
ra1: number;
ra1_2: number;
ra2: number;
ra2_2: number;
ra3: number;
ra3_2: number;
rb1: number;
rb1_2: number;
rb2: number;
rb2_2: number;
rb3: number;
rb3_2: number;
rc1: number;
rc1_2: number;
rc2: number;
rc2_2: number;
rc3: number;
rc3_2: number;
rd1: number;
rd2: number;
rd3: number;
}
/* MeshLength unit is same as fov unit */
const MeshLength = 10;
enum DrawingLineType {
VLine = 0,
VDottedLine = 1,
HLine = 2,
HDottedLine = 3,
}
const LineBrightness = 200;
const DottedLineBrightness = 128;
const EXACTSWITCH: 0 | 1 = 1;
const MaxIntensity = 200;
class M3D {
private data: ArrayBuffer;
private header: M3DHeader | null = null;
private xposView: Int32Array | null = null;
private zposView: Int32Array | null = null;
private yposView: Int32Array | null = null;
private img3dView: Uint8ClampedArray | null = null;
private imageSize = 0; // imagesize size : max (size_x, size_y, size_z)
private centerX = 0; // camera coordinates origin : x
private centerY = 0; // camera coordinates origin : y
private centerZ = 0; // camera coordinates origin : z
private ox = 0; // object coordinates origin : x
private oy = 0; // object coordinates origin : y
private oz = 0; // object coordinates origin : z
constructor(data: ArrayBuffer) {
this.data = data;
this._initialize();
}
isBigEndian(): boolean {
return new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x11;
}
getHeader(): M3DHeader | null {
return this.header;
}
drawImage(canvas: HTMLCanvasElement, ax: number, ay: number, az: number, cutdepth: number, th: number, is3d: boolean, mesh: boolean): boolean {
const irc: M3DImageRenderingContext = {
a1: 0,
a2: 0,
a3: 0,
b1: 0,
b2: 0,
b3: 0,
c1: 0,
c2: 0,
c3: 0,
d1: 0,
d2: 0,
d3: 0,
ra1: 0,
ra1_2: 0,
ra2: 0,
ra2_2: 0,
ra3: 0,
ra3_2: 0,
rb1: 0,
rb1_2: 0,
rb2: 0,
rb2_2: 0,
rb3: 0,
rb3_2: 0,
rc1: 0,
rc1_2: 0,
rc2: 0,
rc2_2: 0,
rc3: 0,
rc3_2: 0,
rd1: 0,
rd2: 0,
rd3: 0,
};
// console.log(ax, ay, az, cutdepth, th, dim, mesh)
if (this.header === null) {
return false;
}
canvas.width = this.imageSize;
canvas.height = this.imageSize;
const ctx = canvas.getContext('2d');
if (ctx === null) {
return false;
}
ctx.fillStyle = 'rgb(0, 0, 0)';
ctx.fillRect(0, 0, this.imageSize, this.imageSize);
const img = ctx.getImageData(0, 0, this.imageSize, this.imageSize);
this._setMatrix(ax, ay, az, irc);
if (is3d) {
const ddimg = [...Array<number>(this.imageSize * this.imageSize)].map(() => 0);
this._makeDIMG(img, cutdepth, th, ddimg, irc);
this._makeShadingImage(img, ddimg);
} else {
this._makeSliceImage(img, cutdepth, irc);
}
if (mesh) {
this._drawMesh(img, irc);
}
ctx.putImageData(img, 0, 0);
return true;
}
private _makeDIMG(img: ImageData, cutdepth: number, th: number, ddimg: number[], irc: M3DImageRenderingContext) {
if (this.header === null) {
return;
}
const pdimg = [...Array<number>(this.imageSize * this.imageSize)].map(() => 0);
const sw = th > this.header.dth ? 1 : th < this.header.dth ? 2 : EXACTSWITCH;
switch (sw) {
case 0: {
this._transSurface(cutdepth, pdimg, irc);
let pos = 0;
for (let j = 0; j < this.imageSize; j++) {
for (let i = 0; i < this.imageSize; i++, pos++) {
ddimg[pos] = pdimg[pos];
const pix = this._getPixel(i, j, cutdepth, irc);
if (pix > th) {
this._drawPixel(img, pos, pix, pix, pix);
ddimg[pos] = cutdepth;
continue;
}
}
}
break;
}
case 1: {
this._transSurface(cutdepth, pdimg, irc);
let pos = 0;
for (let y = 0; y < this.imageSize; y++) {
for (let x = 0; x < this.imageSize; x++, pos++) {
let pdPos = (ddimg[pos] = pdimg[pos]);
if (pdPos === 0) {
continue;
}
const pix = this._getPixel(x, y, cutdepth, irc);
if (pix > th) {
this._drawPixel(img, pos, pix, pix, pix);
ddimg[pos] = cutdepth;
continue;
}
if (pdPos < cutdepth) {
pdPos = cutdepth;
}
ddimg[pos] = 0;
for (let z = pdPos; z < this.imageSize; z++) {
const pix = this._getPixel(x, y, z, irc);
if (pix > th) {
ddimg[pos] = z;
break;
}
}
}
}
break;
}
case 2: {
let pos = 0;
for (let y = 0; y < this.imageSize; y++) {
for (let x = 0; x < this.imageSize; x++, pos++) {
ddimg[pos] = 0;
for (let z = cutdepth; z < this.imageSize; z++) {
const pix = this._getPixel(x, y, z, irc);
if (pix > th) {
if (z === cutdepth) {
this._drawPixel(img, pos, pix, pix, pix);
} else {
ddimg[pos] = z;
}
break;
}
}
}
}
break;
}
}
}
private _makeShadingImage(img: ImageData, ddimg: number[]) {
let pos = 0;
for (let x = 0; x < this.imageSize; x++) {
for (let y = 0; y < this.imageSize; y++, pos++) {
const base = pos * 4;
if (img.data[base] === 0) {
let timg2d = 0;
if (x < this.imageSize - 2 && y < this.imageSize - 2) {
if (ddimg[pos] !== 0) {
const nx = (ddimg[pos] + ddimg[pos + this.imageSize] - ddimg[pos + 2] - ddimg[pos + 2 + this.imageSize]) / 4.0;
const ny = (ddimg[pos] + ddimg[pos + 1] - ddimg[pos + this.imageSize * 2] - ddimg[pos + 1 + this.imageSize * 2]) / 4.0;
const n = Math.sqrt(nx * nx + ny * ny + 1);
timg2d = Math.round(MaxIntensity / n);
}
}
this._drawPixel(img, pos, timg2d, timg2d, timg2d);
}
}
}
}
private _makeSliceImage(img: ImageData, cutdepth: number, irc: M3DImageRenderingContext) {
let pos = 0;
for (let y = 0; y < this.imageSize; y++) {
for (let x = 0; x < this.imageSize; x++, pos++) {
const pix = this._getPixel(x, y, cutdepth, irc);
this._drawPixel(img, pos, pix, pix, pix);
}
}
}
private _drawMesh(img: ImageData, irc: M3DImageRenderingContext) {
let origx: number;
let origy: number;
if (this.header === null) {
return;
}
if (this.header.ox !== 0 && this.header.oy !== 0) {
origx = this.header.ox * irc.ra1 + this.header.oy * irc.rb1 + this.header.oz * irc.rc1 + irc.rd1;
origy = this.header.ox * irc.ra2 + this.header.oy * irc.rb2 + this.header.oz * irc.rc2 + irc.rd2;
} else {
origx = this.centerX;
origy = this.centerY;
}
origx = this._fround(origx, 5);
origy = this._fround(origy, 5);
const band = (this.header.x * MeshLength) / this.header.fov;
const r = 0;
const g = 0;
const b = LineBrightness;
const sr = 0;
const sg = 0;
const sb = DottedLineBrightness;
this._drawLine(img, Math.round(origx), r, g, b, DrawingLineType.VLine);
this._drawLine(img, Math.round(origy), r, g, b, DrawingLineType.HLine);
for (let pos = Math.round(origx) + band; pos < this.imageSize; pos += band) {
this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.VDottedLine);
}
for (let pos = Math.round(origx) - band; pos >= 0; pos -= band) {
this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.VDottedLine);
}
for (let pos = Math.round(origy) + band; pos < this.imageSize; pos += band) {
this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.HDottedLine);
}
for (let pos = Math.round(origy) - band; pos >= 0; pos -= band) {
this._drawLine(img, Math.round(pos), sr, sg, sb, DrawingLineType.HDottedLine);
}
}
private _drawLine(img: ImageData, value: number, r: number, g: number, b: number, type: DrawingLineType) {
switch (type) {
case DrawingLineType.VLine: {
let pos = value;
for (let y = 0; y < this.imageSize; y++, pos += this.imageSize) {
this._drawPixel(img, pos, r, g, b);
}
break;
}
case DrawingLineType.VDottedLine: {
let pos = value;
for (let y = 0; y < this.imageSize; y += 2, pos += this.imageSize * 2) {
this._drawPixel(img, pos, r, g, b);
}
break;
}
case DrawingLineType.HLine: {
let pos = value * this.imageSize;
for (let x = 0; x < this.imageSize; x++, pos++) {
this._drawPixel(img, pos, r, g, b);
}
break;
}
case DrawingLineType.HDottedLine: {
let pos = value * this.imageSize;
for (let x = 0; x < this.imageSize; x += 2, pos += 2) {
this._drawPixel(img, pos, r, g, b);
}
break;
}
default:
break;
}
}
private _drawPixel(img: ImageData, pos: number, r: number, g: number, b: number) {
let base = pos * 4;
img.data[base++] = r;
img.data[base++] = g;
img.data[base++] = b;
img.data[base] = 255;
}
private _getPixel(x: number, y: number, z: number, irc: M3DImageRenderingContext): number {
const len = [...Array<number>(8)].map(() => 0);
const ratio = [...Array<number>(8)].map(() => 0);
const tx = x * irc.a1 + y * irc.b1 + z * irc.c1 + irc.d1;
const ty = x * irc.a2 + y * irc.b2 + z * irc.c2 + irc.d2;
const tz = x * irc.a3 + y * irc.b3 + z * irc.c3 + irc.d3;
let ix = Math.floor(tx);
let iy = Math.floor(ty);
let iz = Math.floor(tz);
if (this.header === null || this.img3dView === null) {
return 0;
}
if (ix < 0 || ix + 1 >= this.header.x || iy < 0 || iy + 1 >= this.header.y || iz < 0 || iz + 1 >= this.header.z) {
return 0;
}
const ax1 = Math.abs(tx - ix);
const ax2 = Math.abs(1 - ax1);
const ay1 = Math.abs(ty - iy);
const ay2 = Math.abs(1 - ay1);
const az1 = Math.abs(tz - iz);
const az2 = Math.abs(1 - az1);
let sumlen1 = 0;
for (let i = 0; i < 8; i++) {
len[i] = i & 1 ? ax2 : ax1;
len[i] += i & 2 ? ay2 : ay1;
len[i] += i & 4 ? az2 : az1;
if (len[i] < 0.001) {
ix += i & 1 ? 1 : 0;
iy += i & 2 ? 1 : 0;
iz += i & 4 ? 1 : 0;
return this.img3dView[ix + iy * this.header.x + iz * this.header.x * this.header.y];
}
sumlen1 += ratio[i] = 1 / len[i];
}
let pos = ix + iy * this.header.x + iz * this.header.x * this.header.y;
let sumlen2 = this.img3dView[pos] * ratio[0];
sumlen2 += this.img3dView[pos + 1] * ratio[1];
sumlen2 += this.img3dView[pos + this.header.x] * ratio[2];
sumlen2 += this.img3dView[pos + 1 + this.header.x] * ratio[3];
pos += this.header.x * this.header.y;
sumlen2 += this.img3dView[pos] * ratio[4];
sumlen2 += this.img3dView[pos + 1] * ratio[5];
sumlen2 += this.img3dView[pos + this.header.x] * ratio[6];
sumlen2 += this.img3dView[pos + 1 + this.header.x] * ratio[7];
return Math.round(sumlen2 / sumlen1);
}
private _transSurface(cutdepth: number, pdimg: number[], irc: M3DImageRenderingContext) {
if (this.header === null || this.xposView === null || this.yposView === null || this.zposView === null) {
return;
}
const ix = [...Array<number>(8)].map(() => 0);
const iy = [...Array<number>(8)].map(() => 0);
const iz = [...Array<number>(8)].map(() => 0);
for (let i = 0; i < this.header.dlen; i++) {
const x = this.xposView[i];
const y = this.yposView[i];
const z = this.zposView[i];
ix[0] = x * irc.ra1 + y * irc.rb1 + z * irc.rc1 + irc.rd1;
iy[0] = x * irc.ra2 + y * irc.rb2 + z * irc.rc2 + irc.rd2;
iz[0] = x * irc.ra3 + y * irc.rb3 + z * irc.rc3 + irc.rd3;
ix[1] = ix[0] + irc.ra1_2;
iy[1] = iy[0] + irc.ra2_2;
iz[1] = iz[0] + irc.ra3_2;
ix[2] = ix[0] + irc.rb1_2;
iy[2] = iy[0] + irc.rb2_2;
iz[2] = iz[0] + irc.rb3_2;
ix[3] = ix[0] + irc.rc1_2;
iy[3] = iy[0] + irc.rc2_2;
iz[3] = iz[0] + irc.rc3_2;
ix[4] = ix[1] + irc.rb1_2;
iy[4] = iy[1] + irc.rb2_2;
iz[4] = iz[1] + irc.rb3_2;
ix[5] = ix[1] + irc.rc1_2;
iy[5] = iy[1] + irc.rc2_2;
iz[5] = iz[1] + irc.rc3_2;
ix[6] = ix[2] + irc.rc1_2;
iy[6] = iy[2] + irc.rc2_2;
iz[6] = iz[2] + irc.rc3_2;
ix[7] = ix[4] + irc.rc1_2;
iy[7] = iy[4] + irc.rc2_2;
iz[7] = iz[4] + irc.rc3_2;
for (let j = 0; j < 8; j++) {
const xi = Math.round(ix[j]);
const yi = Math.round(iy[j]);
if (xi < 0 || xi >= this.imageSize || yi < 0 || yi >= this.imageSize || iz[j] < cutdepth || iz[j] >= this.imageSize) {
continue;
}
const pos = xi + yi * this.imageSize;
if (pdimg[pos] === 0 || pdimg[pos] > iz[j]) {
pdimg[pos] = iz[j];
}
}
}
}
private _setMatrix(x: number, y: number, z: number, irc: M3DImageRenderingContext) {
const xtr = [...Array<number>(9)].map(() => 0);
const ytr = [...Array<number>(9)].map(() => 0);
const ztr = [...Array<number>(9)].map(() => 0);
const tmp = [...Array<number>(9)].map(() => 0);
const tr = [...Array<number>(9)].map(() => 0);
this._setXtr(xtr, (x * Math.PI) / 180.0);
this._setYtr(ytr, (y * Math.PI) / 180.0);
this._setZtr(ztr, (z * Math.PI) / 180.0);
this._multi(ytr, xtr, tmp);
this._multi(ztr, tmp, tr);
irc.a1 = tr[0];
irc.b1 = tr[1];
irc.c1 = tr[2];
irc.a2 = tr[3];
irc.b2 = tr[4];
irc.c2 = tr[5];
irc.a3 = tr[6];
irc.b3 = tr[7];
irc.c3 = tr[8];
irc.d1 = -irc.a1 * this.centerX - irc.b1 * this.centerY - irc.c1 * this.centerZ + this.ox;
irc.d2 = -irc.a2 * this.centerX - irc.b2 * this.centerY - irc.c2 * this.centerZ + this.oy;
irc.d3 = -irc.a3 * this.centerX - irc.b3 * this.centerY - irc.c3 * this.centerZ + this.oz;
this._setXtr(xtr, (-x * Math.PI) / 180.0);
this._setYtr(ytr, (-y * Math.PI) / 180.0);
this._setZtr(ztr, (-z * Math.PI) / 180.0);
this._multi(ytr, ztr, tmp);
this._multi(xtr, tmp, tr);
irc.ra1 = tr[0];
irc.rb1 = tr[1];
irc.rc1 = tr[2];
irc.ra2 = tr[3];
irc.rb2 = tr[4];
irc.rc2 = tr[5];
irc.ra3 = tr[6];
irc.rb3 = tr[7];
irc.rc3 = tr[8];
irc.rd1 = -irc.ra1 * this.ox - irc.rb1 * this.oy - irc.rc1 * this.oz + this.centerX;
irc.rd2 = -irc.ra2 * this.ox - irc.rb2 * this.oy - irc.rc2 * this.oz + this.centerY;
irc.rd3 = -irc.ra3 * this.ox - irc.rb3 * this.oy - irc.rc3 * this.oz + this.centerZ;
irc.ra1_2 = irc.ra1 / 2;
irc.rb1_2 = irc.rb1 / 2;
irc.rc1_2 = irc.rc1 / 2;
irc.ra2_2 = irc.ra2 / 2;
irc.rb2_2 = irc.rb2 / 2;
irc.rc2_2 = irc.rc2 / 2;
irc.ra3_2 = irc.ra3 / 2;
irc.rb3_2 = irc.rb3 / 2;
irc.rc3_2 = irc.rc3 / 2;
}
private _setXtr(xtr: number[], ang: number) {
xtr[0] = 1;
xtr[1] = 0;
xtr[2] = 0;
xtr[3] = 0;
xtr[4] = Math.cos(ang);
xtr[5] = -Math.sin(ang);
xtr[6] = 0;
xtr[7] = Math.sin(ang);
xtr[8] = Math.cos(ang);
}
private _setYtr(ytr: number[], ang: number) {
ytr[0] = Math.cos(ang);
ytr[1] = 0;
ytr[2] = Math.sin(ang);
ytr[3] = 0;
ytr[4] = 1;
ytr[5] = 0;
ytr[6] = -Math.sin(ang);
ytr[7] = 0;
ytr[8] = Math.cos(ang);
}
private _setZtr(ztr: number[], ang: number) {
ztr[0] = Math.cos(ang);
ztr[1] = -Math.sin(ang);
ztr[2] = 0;
ztr[3] = Math.sin(ang);
ztr[4] = Math.cos(ang);
ztr[5] = 0;
ztr[6] = 0;
ztr[7] = 0;
ztr[8] = 1;
}
private _multi(l: number[], r: number[], a: number[]) {
a[0] = l[0] * r[0] + l[1] * r[3] + l[2] * r[6];
a[3] = l[3] * r[0] + l[4] * r[3] + l[5] * r[6];
a[6] = l[6] * r[0] + l[7] * r[3] + l[8] * r[6];
a[1] = l[0] * r[1] + l[1] * r[4] + l[2] * r[7];
a[4] = l[3] * r[1] + l[4] * r[4] + l[5] * r[7];
a[7] = l[6] * r[1] + l[7] * r[4] + l[8] * r[7];
a[2] = l[0] * r[2] + l[1] * r[5] + l[2] * r[8];
a[5] = l[3] * r[2] + l[4] * r[5] + l[5] * r[8];
a[8] = l[6] * r[2] + l[7] * r[5] + l[8] * r[8];
}
private _fround(x: number, n: number) {
return Math.round(x * Math.pow(10, n)) / Math.pow(10, n);
}
private _initialize(): void {
const view = new DataView(this.data);
const magic = view.getInt32(0, true);
const isLittleEndianFile = magic === 0x4d523344;
if (!isLittleEndianFile && magic !== 0x4433524d) {
console.log('invalid data format, this data is not m3d format');
return;
}
let offset = 0;
const decoder = new TextDecoder('utf-8');
this.header = {
magic: view.getInt32(offset, isLittleEndianFile),
x: view.getInt32((offset += 4), isLittleEndianFile),
y: view.getInt32((offset += 4), isLittleEndianFile),
z: view.getInt32((offset += 4), isLittleEndianFile),
filename: decoder.decode(new Uint8Array(this.data, (offset += 4), 256)).replace(/\0+$/, ''),
subject: decoder.decode(new Uint8Array(this.data, (offset += 256), 256)).replace(/\0+$/, ''),
direction: decoder.decode(new Uint8Array(this.data, (offset += 256), 4)).replace(/\0+$/, ''),
date: view.getUint32((offset += 4), isLittleEndianFile),
fov: view.getFloat64((offset += 4), isLittleEndianFile),
weight: view.getFloat64((offset += 8), isLittleEndianFile),
ax: view.getFloat64((offset += 8), isLittleEndianFile),
ay: view.getFloat64((offset += 8), isLittleEndianFile),
az: view.getFloat64((offset += 8), isLittleEndianFile),
ox: view.getFloat64((offset += 8), isLittleEndianFile),
oy: view.getFloat64((offset += 8), isLittleEndianFile),
oz: view.getFloat64((offset += 8), isLittleEndianFile),
dlen: view.getInt32((offset += 8), isLittleEndianFile),
dth: view.getInt32((offset += 4), isLittleEndianFile),
memo: decoder.decode(new Uint8Array(this.data, (offset += 4), 256)).replace(/\0+$/, ''),
};
// swap byte order if endian is not matched
if ((this.isBigEndian() && isLittleEndianFile) || (!this.isBigEndian() && !isLittleEndianFile)) {
const start = 864;
const end = start + this.header.dlen * 4 * 3;
for (let i = start; i < end; i += 4) {
const data = new Uint8Array(this.data, i, 4);
data.reverse();
}
}
this.xposView = new Int32Array(this.data, 864, this.header.dlen);
this.yposView = new Int32Array(this.data, 864 + this.header.dlen * 4, this.header.dlen);
this.zposView = new Int32Array(this.data, 864 + this.header.dlen * 8, this.header.dlen);
this.img3dView = new Uint8ClampedArray(this.data, 864 + this.header.dlen * 12, this.header.x * this.header.y * this.header.z);
// set image generation offset
this.imageSize = Math.ceil(Math.max(this.header.x, this.header.y, this.header.z) * Math.sqrt(3.0)) + 1;
this.centerX = (this.imageSize - 1) / 2.0;
this.centerY = (this.imageSize - 1) / 2.0;
this.centerZ = (this.imageSize - 1) / 2.0;
this.ox = (this.header.x - 1) / 2.0;
this.oy = (this.header.y - 1) / 2.0;
this.oz = (this.header.z - 1) / 2.0;
}
}
export default M3D;

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

@@ -0,0 +1,41 @@
import React, { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
import ReactGA from "react-ga4";
import { useLocation } from "react-router-dom";
import Config, { MultiLang } from "../config";
import Page from "../custom/Page";
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);
}, [cookies, setCookie, lang, location]);
return <Page lang={lang} />;
};
const AppRoot: React.FC = () => {
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== "") {
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID);
}
return <AppMain />;
};
export default AppRoot;

View File

@@ -0,0 +1,35 @@
import React from "react";
import { Navigate } from "react-router";
import { 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 getRedirectUrl = () => {
const { pathname } = location;
switch (pathname || "") {
case "/index.php": {
return "/";
}
}
return "";
};
if (location.pathname === "/") {
return null;
}
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,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,33 @@
import React from "react";
import { Link, useLocation } from "react-router-dom";
import { MultiLang } from "../../config";
import mlangEnglish from "../assets/images/mlang_english.gif";
import mlangJapanese from "../assets/images/mlang_japanese.gif";
interface Props {
lang: MultiLang;
className?: string;
image?: string;
}
const langResources = {
en: { image: mlangEnglish, title: "English" },
ja: { image: mlangJapanese, title: "Japanese" },
};
const LangFlag: React.FC<Props> = (props: Props) => {
const { lang, className, image } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const flagLang = lang === "en" ? "ja" : "en";
params.set("ml_lang", flagLang);
const url = location.pathname + "?" + params.toString();
const imageSrc = image || langResources[flagLang].image;
return (
<Link className={className} to={url}>
<img src={imageSrc} alt={langResources[flagLang].title} title={langResources[flagLang].title} />
</Link>
);
};
export default LangFlag;

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,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;

15
src/config.ts Normal file
View File

@@ -0,0 +1,15 @@
const SITE_TITLE = "NeuroImaging Platform";
const SITE_SLOGAN = "";
const GOOGLE_ANALYTICS_TRACKING_ID = "UA-3757403-1";
const XOONIPS_ITEMTYPES = ["book", "paper", "presentation", "data", "tool", "nimgcenter", "model", "url"];
export type MultiLang = "en" | "ja";
const Config = {
SITE_TITLE,
SITE_SLOGAN,
GOOGLE_ANALYTICS_TRACKING_ID,
XOONIPS_ITEMTYPES,
};
export default Config;

47
src/credits/Credits.tsx Normal file
View File

@@ -0,0 +1,47 @@
import React from "react";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import CreditsIndex from "./CreditsIndex";
import CreditsPage from "./CreditsPage";
interface Props {
lang: MultiLang;
}
const CreditsTop: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const paramId = params.get("id");
if (null === paramId) {
return <CreditsIndex lang={lang} />;
}
if (/^[1-9][0-9]*$/.test(paramId)) {
const id = parseInt(paramId, 10);
return <CreditsPage lang={lang} id={id} />;
}
return <PageNotFound lang={lang} />;
};
const CreditsRedirectToTop: React.FC = () => {
const location = useLocation();
const url = location.pathname + "index.php" + location.search + location.hash;
return <Navigate to={url} />;
};
const Credits: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<Routes>
<Route path="" element={<CreditsRedirectToTop />} />
<Route path="index.php" element={<CreditsTop lang={lang} />} />
<Route path="aboutus.php" element={<CreditsPage lang={lang} id={0} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
};
export default Credits;

View File

@@ -0,0 +1,59 @@
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { Link } from "react-router-dom";
import Loading from "../common/lib/Loading";
import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived";
import PageNotFound from "../common/lib/PageNotFound";
import { MultiLang } from "../config";
import Functions from "../functions";
import CreditsUtils, { CreditsIndexData } from "./lib/CreditsUtils";
interface Props {
lang: MultiLang;
}
const CreditsIndex: React.FC<Props> = (props: Props) => {
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [index, setIndex] = useState<CreditsIndexData | null>(null);
useEffect(() => {
const updatePage = async () => {
const index = await CreditsUtils.getIndex();
setLoading(false);
setIndex(index);
};
updatePage();
}, [lang]);
if (loading) {
return <Loading />;
}
if (null === index) {
return <PageNotFound lang={lang} />;
}
const mtitle = Functions.mlang(index.name, lang);
return (
<>
<Helmet>
<title>
{mtitle} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{mtitle}</h3>
<NoticeSiteHasBeenArchived lang={lang} />
<ul>
{index.pages.map((page) => {
const title = Functions.mlang(page.title, lang);
const url = CreditsUtils.getPageUrl(page.id);
return (
<li key={page.id}>
<Link to={url}>{title}</Link>
</li>
);
})}
</ul>
</>
);
};
export default CreditsIndex;

View File

@@ -0,0 +1,62 @@
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import Loading from "../common/lib/Loading";
import NoticeSiteHasBeenArchived from "../common/lib/NoticeSiteHasBeenArchived";
import PageNotFound from "../common/lib/PageNotFound";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import Functions from "../functions";
import CreditsUtils, { CreditsIndexData, CreditsPageDetailData } from "./lib/CreditsUtils";
interface Props {
lang: MultiLang;
id: number;
}
const CreditsPage: React.FC<Props> = (props: Props) => {
const { lang, id } = props;
const [loading, setLoading] = useState<boolean>(true);
const [index, setIndex] = useState<CreditsIndexData | null>(null);
const [page, setPage] = useState<CreditsPageDetailData | null>(null);
useEffect(() => {
const updatePage = async () => {
const index = await CreditsUtils.getIndex();
const page = await CreditsUtils.getPage(id);
setLoading(false);
setIndex(index);
setPage(page);
};
updatePage();
}, [id]);
if (loading) {
return <Loading />;
}
if (page === null || index === null) {
return <PageNotFound lang={lang} />;
}
const mtitle = Functions.mlang(index.name, lang);
const title = Functions.mlang(page.title, lang);
const lastupdate = page.lastupdate === 0 ? "" : Functions.formatDate(page.lastupdate, "MMMM Do, YYYY");
return (
<>
<Helmet>
<title>
{title} - {mtitle} - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h3>{title}</h3>
<NoticeSiteHasBeenArchived lang={lang} />
{"" !== lastupdate && (
<div style={{ textAlign: "right" }}>
{Functions.mlang("[en]Last Update[/en][ja]最終更新日[/ja]", lang)} : {lastupdate}
</div>
)}
<hr />
<XoopsCode lang={lang} text={page.content} dohtml={true} />
</>
);
};
export default CreditsPage;

View File

@@ -0,0 +1,47 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Loading from "../../common/lib/Loading";
import PageNotFound from "../../common/lib/PageNotFound";
import { MultiLang } from "../../config";
import Functions from "../../functions";
import CreditsUtils, { CreditsMenuData } from "../lib/CreditsUtils";
interface Props {
lang: MultiLang;
}
const CreditsMenu: React.FC<Props> = (props: Props) => {
const { lang } = props;
const [loading, setLoading] = useState<boolean>(true);
const [menu, setMenu] = useState<CreditsMenuData[] | null>(null);
useEffect(() => {
const updatePage = async () => {
const menu = await CreditsUtils.getMenu();
setLoading(false);
setMenu(menu);
};
updatePage();
}, [lang]);
if (loading) {
return <Loading />;
}
if (menu === null || menu.length === 0) {
return <PageNotFound lang={lang} />;
}
const links = menu.map((item, idx) => {
const title = Functions.mlang(item.title, lang);
const style = idx === 0 ? "menuTop" : "menuMain";
return (
<li key={idx}>
<Link className={style} to={item.link}>
{title}
</Link>
</li>
);
});
return <ul className="mainmenu">{links}</ul>;
};
export default CreditsMenu;

View File

@@ -0,0 +1,77 @@
import axios from "axios";
export interface CreditsPageData {
id: number;
title: string;
}
export interface CreditsPageDetailData extends CreditsPageData {
content: string;
lastupdate: number;
}
export interface CreditsIndexData {
name: string;
pages: CreditsPageData[];
}
export interface CreditsMenuData {
title: string;
link: string;
}
class CreditsUtils {
private pathname: string;
private index?: CreditsIndexData | null;
constructor(path: string) {
this.pathname = path;
}
getIndexUrl(): string {
return this.pathname + "/";
}
getPageUrl(id: number): string {
return this.getIndexUrl() + (0 === id ? "aboutus.php" : "?id=" + id);
}
async getIndex(): Promise<CreditsIndexData | null> {
if (typeof this.index === "undefined") {
try {
const url = this.getIndexUrl() + "index.json";
const response = await axios.get(url);
const index = response.data as CreditsIndexData;
this.index = index;
} catch (err) {
this.index = null;
}
}
return this.index;
}
async getMenu(): Promise<CreditsMenuData[]> {
const menu: CreditsMenuData[] = [];
const index = await this.getIndex();
if (index !== null) {
index.pages.forEach((page) => {
menu.push({ title: page.title, link: this.getPageUrl(page.id) });
});
}
return menu;
}
async getPage(id: number): Promise<CreditsPageDetailData | null> {
let page = null;
try {
const url = this.getIndexUrl() + id + ".json";
const response = await axios.get(url);
page = response.data as CreditsPageDetailData;
} catch (err) {
// ignore
}
return page;
}
}
export default new CreditsUtils("/modules/credits");

52
src/custom/Footer.tsx Normal file
View File

@@ -0,0 +1,52 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { HashLink } from "react-router-hash-link";
import { MultiLang } from "../config";
import imageBannerNiu from "./assets/images/banner-niu.png";
import imageBannerRikenCbs from "./assets/images/banner-riken-cbs.png";
import imageBannerRiken from "./assets/images/banner-riken.png";
import imageBannerXoonips from "./assets/images/banner-xoonips.png";
interface Props {
lang: MultiLang;
}
const Footer: React.FC<Props> = (props: Props) => {
const location = useLocation();
return (
<footer id="footer">
<div className="pageTop">
<HashLink to={`${location.pathname}${location.search}#page`}>
<span className="hidden">Go Page Top</span>
</HashLink>
</div>
<div className="link">
<span>
<a href="http://www.riken.jp/" title="RIKEN" target="_blank" rel="noopener noreferrer">
<img src={imageBannerRiken} alt="RIKEN" />
</a>
</span>
<span>
<a href="https://cbs.riken.jp/" title="RIKEN Center for Brain Science" target="_blank" rel="noopener noreferrer">
<img src={imageBannerRikenCbs} alt="RIKEN Center for Brain Science" />
</a>
</span>
<span>
<a href="https://www.ni.riken.jp/" title="Neuroinformatics Unit, RIKEN Center for Brain Science" target="_blank" rel="noopener noreferrer">
<img src={imageBannerNiu} alt="Neuroinformatics Unit, RIKEN Center for Brain Science" />
</a>
</span>
<span>
<a href="http://xoonips.osdn.jp/" title="XooNIps" target="_blank" rel="noopener noreferrer">
<img src={imageBannerXoonips} alt="XooNIps" />
</a>
</span>
</div>
<div className="copyright">
<span>Copyright (C) 2018 Neuroinformatics Unit, RIKEN Center for Brain Science</span>
</div>
</footer>
);
};
export default Footer;

73
src/custom/Header.tsx Normal file
View File

@@ -0,0 +1,73 @@
import React from "react";
import { Link } from "react-router-dom";
import LangFlag from "../common/lib/LangFlag";
import Config, { MultiLang } from "../config";
import imageJnode from "./assets/images/jnode.png";
interface Props {
lang: MultiLang;
}
const Header: React.FC<Props> = (props: Props) => {
return (
<header id="header">
<div className="logo">
<Link to="/" title="Home">
<span className="hidden">{Config.SITE_TITLE}</span>
</Link>
</div>
<div className="jnode">
<a href="https://www.neuroinf.jp/" target="_blank" rel="noopener noreferrer" title="INCF Japan Node">
<img src={imageJnode} alt="INCF Japan Node" />
</a>
</div>
<nav className="menubar">
<ul className="mainmenu">
<li>
<Link id="hmm01" to="/modules/nimgdocs/index.php?docname=about_NIMGPF" title="NIMG-PFの基本理念、委員会構成、取り扱い説明等を掲載しています。">
NIMG-PFについて About This Site
</Link>
</li>
<li>
<Link id="hmm02" to="/modules/xoonips/" title="データベースを検索することができます。">
XooNIpsモード XooNIps database mode
</Link>
</li>
<li>
<Link id="hmm03" to="/modules/nimgsearch/" title="脳図から関連する文献を検索することができます。">
search with Brain Atlas
</Link>
</li>
<li>
<Link id="hmm04" to="/modules/nimgdocs/tutorials/" title="脳イメージングについて説明した教育資料を閲覧できます。">
NIMG-PF Tutorials
</Link>
</li>
<li>
<Link id="hsm01" to="/modules/xnpnimgcenter/utilities/" title="ユーティリティ">
Utilities
</Link>
</li>
<li>
<Link id="hsm03" to="/modules/nimgdocs/index.php?docname=3DBrowse" title="日本人脳データ">
3D Browse
</Link>
</li>
<li>
<Link id="hsm04" to="/modules/nimgdocs/index.php?docname=3DBrowse" title="日本ザル脳データ">
3D Browse
</Link>
</li>
<li>
<div className="lang">
<LangFlag lang="en" />
<LangFlag lang="ja" />
</div>
</li>
</ul>
</nav>
</header>
);
};
export default Header;

148
src/custom/Page.tsx Normal file
View File

@@ -0,0 +1,148 @@
import React from "react";
import { Outlet, Route, Routes } from "react-router-dom";
import BrainExplorerIndex from "../brainexplorer/BrainExplorer";
import XoopsPathRedirect from "../common/XoopsPathRedirect";
import { MultiLang } from "../config";
import CreditsMenu from "../credits/blocks/CreditsMenu";
import Credits from "../credits/Credits";
import NimgcenterUtilities from "../nimgcenter/NimgenterUtilities";
import Nimgdocs from "../nimgdocs/Nimgdocs";
import Nimgsearch from "../nimgsearch/Nimgsearch";
import IndexTree from "../xoonips/blocks/IndexTree";
import Ranking from "../xoonips/blocks/Rankings";
import RecentContents from "../xoonips/blocks/RecentContents";
import Search2 from "../xoonips/blocks/Search";
import Xoonips from "../xoonips/Xoonips";
import BulletinBoard from "./blocks/BulletinBoard";
import HowToUseLinks from "./blocks/HowToUseLinks";
import HowToUseVideo from "./blocks/HowToUseVideo";
import Information from "./blocks/Information";
import Questionnaire from "./blocks/Questionnaire";
import RecentStatus from "./blocks/RecentStatus";
import Footer from "./Footer";
import Header from "./Header";
interface Props {
lang: MultiLang;
}
const MainContent: React.FC<Props> = (props: Props) => {
return (
<div id="main">
<div className="mainContent">
<Outlet />
</div>
</div>
);
};
const CenterColumn: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div id="centercolumn">
<Outlet />
<Routes>
<Route path="/*" element={<MainContent lang={lang} />}>
<Route path="modules/credits/*" element={<Credits lang={lang} />} />
<Route path="modules/xoonips/*" element={<Xoonips lang={lang} />} />
<Route path="modules/nimgdocs/*" element={<Nimgdocs lang={lang} />} />
<Route path="modules/nimgsearch/*" element={<Nimgsearch lang={lang} />} />
<Route path="modules/xnpnimgcenter/utilities/*" element={<NimgcenterUtilities lang={lang} />} />
<Route path="modules/brainexplorer/*" element={<BrainExplorerIndex lang={lang} />} />
<Route path="*" element={<XoopsPathRedirect lang={lang} />} />
</Route>
</Routes>
</div>
);
};
const CenterColumnBlock: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<div id="centerCcolumn">
<Information lang={lang} />
<BulletinBoard lang={lang} />
<HowToUseVideo lang={lang} />
<HowToUseLinks lang={lang} />
<Questionnaire lang={lang} />
<RecentStatus lang={lang} />
</div>
<div className="col2">
<div id="centerLcolumn">
<div className="block">
<div className="blockTitle">XooNIps Ranking</div>
<div className="blockContent">
<Ranking lang={lang} />
</div>
</div>
</div>
<div id="centerRcolumn">
<div className="block">
<div className="blockTitle">XooNIps Update</div>
<div className="blockContent">
<RecentContents lang={lang} />
</div>
</div>
</div>
</div>
</>
);
};
const LeftColumn: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div id="leftcolumn">
<Outlet />
<div className="block">
<div className="blockTitle">Index Tree</div>
<div className="blockContent">
<IndexTree lang={lang} />
</div>
</div>
<div className="block">
<div className="blockTitle">Site Information</div>
<div className="blockContent">
<CreditsMenu lang={lang} />
</div>
</div>
</div>
);
};
const XoopsSearchBlock: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div className="block">
<div className="blockTitle">Item Search</div>
<div className="blockContent">
<Search2 lang={lang} />
</div>
</div>
);
};
const Page: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div id="page">
<Header lang={lang} />
<div className="col2">
<Routes>
<Route path="/*" element={<CenterColumn lang={lang} />}>
<Route index element={<CenterColumnBlock lang={lang} />} />
</Route>
</Routes>
<Routes>
<Route path="/*" element={<LeftColumn lang={lang} />}>
<Route path="modules/xoonips/*" element={<XoopsSearchBlock lang={lang} />} />
</Route>
</Routes>
</div>
<Footer lang={lang} />
</div>
);
};
export default Page;

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

396
src/custom/assets/style.css Normal file
View File

@@ -0,0 +1,396 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
#root {
width: 100%;
min-width: 860px;
max-width: 1500px;
}
body {
font-family: Arial, Helvetica, sans-serif;
color: #000000;
background-color: #fffff8;
margin: 0;
font-size: 80%;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: Georgia, "Times New Roman", Times, serif;
margin-bottom: 0.5rem;
font-weight: bold;
line-height: 1.2;
color: inherit;
}
h1 {
font-size: 1.802em;
}
h2 {
font-size: 1.602em;
}
h3 {
font-size: 1.424em;
}
h4 {
font-size: 1.266em;
}
h5 {
font-size: 1.125em;
}
h6 {
font-size: 1em;
}
a {
color: #ff6600;
text-decoration: none;
}
a:hover {
color: #ff0000;
text-decoration: underline;
}
hr {
border-top: 0;
border-right: 0;
border-bottom: 1px solid #cccccc;
border-left: 0;
}
ul,
ol {
margin: 0.5em 0;
padding: 0 0.5em 0 2em;
}
li {
line-height: 1.5;
padding: 0.5em 0;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 2px;
}
td,
th {
padding: 5px;
}
th,
td,
.head {
vertical-align: top;
}
.foot {
vertical-align: middle;
}
th,
.head,
.foot {
font-weight: bold;
background-color: #fef6ef;
border-bottom: 1px solid #cacaca;
border-right: 1px solid #cacaca;
}
tr.even > td,
.even {
background-color: #fdfaf7;
border-bottom: 1px solid #cacaca;
border-right: 1px solid #cacaca;
}
tr.odd > td,
.odd {
background-color: #ffffff;
border-bottom: 1px solid #cacaca;
border-right: 1px solid #cacaca;
}
.outer {
border: 1px solid #cacaca;
}
.clearfix::after {
content: "";
display: block;
clear: both;
}
.hidden {
display: none;
}
#page {
margin: 0 auto;
color: #666666;
background: #fffff8 url(images/back.png) repeat-x;
}
#header {
position: relative;
height: 153px;
}
#header .logo {
margin: 0;
padding: 0;
position: absolute;
left: 0;
top: 6px;
height: 81px;
width: 345px;
background-image: url(images/logo.png);
}
#header .logo a {
display: inline-block;
height: 81px;
width: 345px;
}
#header .jnode {
position: absolute;
right: 20px;
top: 19px;
}
#header .menubar {
position: absolute;
left: 0;
top: 92px;
}
#header .menubar ul.mainmenu {
text-align: left;
list-style: none;
margin: 0;
padding: 0;
}
#header .menubar ul.mainmenu li {
float: left;
margin: 0;
padding: 0;
}
#hmm01 {
display: block;
height: 54px;
width: 159px;
background: url(images/mmenu_01.png) no-repeat;
text-indent: -9999px;
}
#hmm02 {
display: block;
height: 54px;
width: 151px;
background: url(images/mmenu_02.png) no-repeat;
text-indent: -9999px;
}
#hmm03 {
display: block;
height: 54px;
width: 160px;
background: url(images/mmenu_03.png) no-repeat;
text-indent: -9999px;
}
#hmm04 {
display: block;
height: 54px;
width: 150px;
background: url(images/mmenu_04.png) no-repeat;
text-indent: -9999px;
}
#hsm01 {
display: block;
height: 54px;
width: 60px;
background: url(images/smenu_01.png) no-repeat;
text-indent: -9999px;
}
#hsm03 {
display: block;
height: 54px;
width: 58px;
background: url(images/smenu_03.png) no-repeat;
text-indent: -9999px;
}
#hsm04 {
display: block;
height: 54px;
width: 58px;
background: url(images/smenu_04.png) no-repeat;
text-indent: -9999px;
}
#header .menubar ul.mainmenu li .lang {
padding-top: 30px;
margin-left: 7px;
}
#header .menubar ul.mainmenu li .lang a {
margin: 0 3px;
}
#footer .pageTop {
margin: 10px 10px 0;
text-align: right;
}
#footer .pageTop a {
display: inline-block;
width: 65px;
height: 19px;
text-decoration: none;
background: url(images/page_top.png) no-repeat;
}
#footer .link {
text-align: right;
padding: 5px 10px;
}
#footer .link span {
margin-left: 10px;
}
#footer .copyright {
padding: 16px 16px 30px;
text-align: right;
color: #ffcb00;
background: #535353 url(images/footer_back.png) repeat-x;
}
.col2::after {
content: "";
display: block;
clear: both;
}
#centercolumn {
float: right;
padding: 15px;
width: 75%;
min-height: 500px;
}
#leftcolumn {
margin-top: 5px;
float: left;
overflow: hidden;
width: 25%;
max-width: 400px;
background-color: #343434;
border-radius: 0 10px 10px 0;
padding: 8px 8px 8px 0;
}
.block:not(:first-child) {
margin-top: 10px;
}
#leftcolumn .block .blockTitle {
font-weight: bold;
height: 27px;
padding: 5px 10px 0 23px;
border-radius: 0 5px 5px 0;
box-shadow: 0 3px 8px rgba(255, 255, 255, 0.5) inset;
color: #fdfdfd;
background: url(images/blockTitleIndent12.png) no-repeat 8px center #ef6e00;
white-space: nowrap;
}
#leftcolumn .block .blockContent {
margin-top: 3px;
padding: 5px;
background-color: #d7d7d7;
color: #333333;
border-radius: 0 5px 5px 0;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3) inset;
}
#leftcolumn .block .blockContent a {
color: #333333;
}
#leftcolumn .block .blockContent a:hover {
color: #ff6600;
text-decoration: none;
}
#leftcolumn .block .blockContent ul.mainmenu {
margin: 5px 0;
padding-left: 0;
}
#leftcolumn .block .blockContent ul li {
padding: 0;
}
#leftcolumn .block .blockContent .mainmenu a.menuMain {
padding-left: 3px;
}
#leftcolumn .blockContent .mainmenu a,
#leftcolumn .blockContent .usermenu a {
display: block;
margin-bottom: 4px;
padding: 3px;
border-bottom: 1px dotted #aaa;
}
#centerCcolumn .block .blockTitle {
font-weight: bold;
height: 30px;
padding: 8px 10px 0 28px;
border-radius: 3px 3px 3px 3px;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5),
0 0 10px rgba(255, 255, 255, 0.5) inset;
color: #fdfdfd;
background: url(images/blockTitleIndent16.png) no-repeat 8px center #ef6e00;
white-space: nowrap;
}
#centerCcolumn .block .blockContent {
margin-top: 3px;
padding: 5px;
}
#centerLcolumn {
float: left;
width: 49%;
}
#centerRcolumn {
float: right;
width: 49%;
}
#centerLcolumn .block .blockTitle,
#centerRcolumn .block .blockTitle {
margin: 5px 0;
padding: 3px 5px;
color: #555555;
font-weight: bold;
font-size: 110%;
background: url(images/blockTitleBack.png) repeat;
white-space: nowrap;
}
#main h1 {
background:url(images/h1_indent.png) no-repeat left center;
padding: 5px 0 5px 33px;
}
#main h2 {
background:url(images/h2_background.png);
padding: 5px;
}

View File

@@ -0,0 +1,23 @@
import React from "react";
import { Link } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const BulletinBoard: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<div className="block">
<div className="blockTitle">{Functions.mlang("[en]Bulletin Board[/en][ja]掲示板[/ja]", lang)}</div>
<div className="blockContent">
<p style={{ fontWeight: "bold", textAlign: "center" }}>{Functions.mlang("[en]There is no post yet.[/en][ja]まだ投稿がありません[/ja]", lang)}</p>
<Link to="/modules/nimgdocs/bulletin/log.php">{Functions.mlang("[en]View expired posts[/en][ja]掲示期間が終了した投稿を見る[/ja]", lang)}</Link>
</div>
</div>
);
};
export default BulletinBoard;

View File

@@ -0,0 +1,40 @@
import React from "react";
import { Link } from "react-router-dom";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const HowToUseLinks: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]How to use NIMG-PF[/en][ja]NIMG-PFの使い方[/ja]";
const content = {
en: (
<ul>
<li>
<a href="/modules/nimgdocs/docs/pdf/NIMGPFmanual_en.pdf">NeuroImaging-Plarform (NIMG-PF) User Manual</a>
</li>
</ul>
),
ja: (
<ul>
<li>
<a href="/modules/nimgdocs/docs/pdf/NIMGPFmanual_ja.pdf">(NIMG-PF)</a>
</li>
<li>
<Link to="/modules/nimgdocs/index.php?docname=introduction">NIMG-PFの機能と使い方 </Link>
</li>
</ul>
),
};
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">{content[lang]}</div>
</div>
);
};
export default HowToUseLinks;

View File

@@ -0,0 +1,66 @@
import React, { useState } from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const HowToUseVideo: React.FC<Props> = (props) => {
const { lang } = props;
const [skip, setSkip] = useState<boolean>(false);
const onClickShowMovie = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
e.preventDefault();
setSkip(false);
};
const onClickSkipMovie = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
e.preventDefault();
setSkip(true);
};
const url = "/modules/nimgdocs/docs/viewlet/";
const name = "ty-02" + (lang === "en" ? "eng" : "jap");
const preview = `${url}/${name}_small.png`;
const poster = `${url}/${name}.png`;
const movieWebm = `${url}/${name}.webm`;
const movieMp4 = `${url}/${name}.mp4`;
return (
<div className="block">
<div className="blockContent">
<div style={{ textAlign: "center" }}>
<div style={{ fontWeight: "bold", fontSize: "15pt", margin: "0 0 10px 0" }}>{Functions.mlang("[en]Function and how to use NIMG-PF[/en][ja]NIMG-PFの機能と使い方[/ja]", lang)}</div>
{skip ? (
<>
<div>
<a href="/" onClick={onClickShowMovie}>
Show Movie
</a>
</div>
<img src={preview} alt="How to Use" />
</>
) : (
<>
<div>
<a href="/" onClick={onClickSkipMovie}>
Skip Movie
</a>
</div>
<video poster={poster} autoPlay loop>
<source type="video/mp4" src={movieMp4} />
<source type="video/webm" src={movieWebm} />
<p>
Your browser doesn't support HTML5 video. Here is a <a href={movieMp4}>link to the video</a> instead.
</p>
</video>
</>
)}
</div>
</div>
</div>
);
};
export default HowToUseVideo;

View File

@@ -0,0 +1,29 @@
import React from "react";
import NoticeSiteHasBeenArchived from "../../common/lib/NoticeSiteHasBeenArchived";
import XoopsCode from "../../common/lib/XoopsCode";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const Information: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]Information[/en][ja]お知らせ[/ja]";
const content = {
en: "This site was opened to the public at 23 Jun 2009. <s>When you submit an application form of user registration, please write it correctly. The application may not be approved, if it includes incorrect information or only abbreviation for address, organization, and division name.</s> (Currently, user registration is closed.)",
ja: "このサイトは2009年6月23日に一般公開され、<s>一般ユーザの登録を受け付けています。ユーザ登録されると、データ登録やダウンロードが可能になるなど、より多くの機能をご利用出来ます。ユーザ登録の際には、正確な情報を入力していただくようお願いします。不正確な情報が記入されていたり、住所・機関・所属などが短縮形だけであるなどの場合は、登録が承認されないことがあります。</s> (現在、新規登録を受け付けておりません。)",
};
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">
<NoticeSiteHasBeenArchived lang={lang} />
<XoopsCode lang={lang} text={content[lang]} dohtml={true} />
</div>
</div>
);
};
export default Information;

View File

@@ -0,0 +1,31 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const Questionnaire: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]Questionnaire and Contact address[/en][ja]NIMG-PFの使い方[/ja]";
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent">
<div>
{Functions.mlang("[en]Please fill out the questionnaire.[/en][ja]アンケートにご協力ください[/ja]", lang)} {" "}
<a href="https://spreadsheets.google.com/viewform?formkey=cEpsb3doVHlOTUJ0UHcxQ3k1TFNHSlE6MA" target="_blank" rel="noopener noreferrer">
{Functions.mlang("[en]Go to the page of questionnaire.[/en][ja]アンケートのページへ[/ja]", lang)}
</a>
</div>
<div>{Functions.mlang("[en]Our e-mail address is as follows. If you have any questions, please let me know.[/en][ja]NIMG-PFについてご質問等ありましたら、以下までお願いします。[/ja]", lang)}</div>
<p style={{ textAlign: "center", textDecoration: "underline" }}>
{Functions.mlang("[en]Contact information of NIMG-PF[/en][ja]NIMG-PFについてのご連絡先[/ja]", lang)}: <a href="mailto:NIMG-info@ml.nict.go.jp">NIMG-info@ml.nict.go.jp</a>
</p>
</div>
</div>
);
};
export default Questionnaire;

View File

@@ -0,0 +1,20 @@
import React from "react";
import { MultiLang } from "../../config";
import Functions from "../../functions";
interface Props {
lang: MultiLang;
}
const RecentStatus: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = "[en]NIMG-PF recent status[/en][ja]NIMG-PF更新情報[/ja]";
return (
<div className="block">
<div className="blockTitle">{Functions.mlang(title, lang)}</div>
<div className="blockContent"></div>
</div>
);
};
export default RecentStatus;

136
src/functions.ts Normal file
View File

@@ -0,0 +1,136 @@
import moment from "moment";
import XRegExp from "xregexp";
import Config, { MultiLang } from "./config";
const escape = (str: string): string => {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
return "%" + c.charCodeAt(0).toString(16);
});
};
const unescape = (str: string): string => {
return decodeURIComponent(str);
};
const htmlspecialchars = (str: string): string => {
return str.replace(/(<|>|&|'|")/g, (match) => {
switch (match) {
case "<":
return "&lt;";
case ">":
return "&gt;";
case "&":
return "&amp;";
case "'":
return "&#039;";
case '"':
return "&quot;";
}
return "";
});
};
const camelCase = (str: string): string => {
return str.replace(/[-_](.)/g, (...matches) => {
return matches[1].toUpperCase();
});
};
const snakeCase = (str: string): string => {
var camel = camelCase(str);
return camel.replace(/[A-Z]/g, (...matches) => {
return "_" + matches[0].charAt(0).toLowerCase();
});
};
const pascalCase = (str: string): string => {
var camel = camelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
};
const base64Encode = (str: string): string => {
return btoa(unescape(encodeURIComponent(str)));
};
const base64Decode = (str: string): string => {
return decodeURIComponent(escape(atob(str)));
};
const formatDate = (timestamp: number, format: string): string => {
return moment(new Date(timestamp * 1000)).format(format);
};
const ordinal = (num: number): string => {
const hundred = num % 100;
if (hundred >= 10 && hundred <= 20) {
return "th";
}
const ten = num % 10;
const suffix = ["st", "nd", "rd"];
return ten > 0 && ten < 4 ? suffix[ten - 1] : "th";
};
const mlang = (str: string, lang: MultiLang): string => {
const mlLangs = ["en", "ja"];
// escape brackets inside of <input type="text" value="...">
let text = str.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/\[/g, "__ml[ml__") + m3;
}
return whole;
});
// escape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (whole, m1, m2, m3) => {
return m1 + m2.replace(/\[/g, "__ml[ml__") + m3;
});
// simple pattern to strip selected lang_tags
const re = new RegExp("\\[/?(?:[^\\]]+\\|)?" + lang + "(?:\\|[^\\]]+)?\\](?:<br />)?", "g");
text = text.replace(re, "");
// eliminate description between the other language tags.
mlLangs.forEach((mlLang) => {
if (mlLang !== lang) {
const re = XRegExp("\\[(?:[^/][^\\]]+\\|)?" + mlLang + "(?:\\|[^\\]]+)?\\].*?\\[/(?:[^\\]]+\\|)?" + mlLang + "(?:\\|[^\\]]+)?\\](?:<br />)?", "isg");
text = text.replace(re, (whole) => {
return whole.match(/<\/table>/) ? whole : "";
});
}
});
// unescape brackets inside of <textarea></textarea>
text = text.replace(/(<textarea[^>]*>)(.*)(<\/textarea>)/gis, (whole, m1, m2, m3) => {
return m1 + m2.replace(/__ml\[ml__/g, "[") + m3;
});
// unescape brackets inside of <input type="text" value="...">
text = text.replace(/(<input)([^>]*)(>)/gis, (whole, m1, m2, m3) => {
if (m2.match(/type=["']?(?:text|hidden)["']?/is)) {
return m1 + m2.replace(/__ml\[ml__/g, "[") + m3;
}
return whole;
});
return text;
};
const siteTitle = (lang: MultiLang): string => {
return mlang(Config.SITE_TITLE, lang);
};
const siteSlogan = (lang: MultiLang): string => {
return mlang(Config.SITE_SLOGAN, lang);
};
const Functions = {
escape,
unescape,
htmlspecialchars,
camelCase,
snakeCase,
pascalCase,
base64Encode,
base64Decode,
formatDate,
ordinal,
mlang,
siteTitle,
siteSlogan,
};
export default Functions;

1
src/index.css Normal file
View File

@@ -0,0 +1 @@
@import-normalize;

18
src/index.tsx Normal file
View File

@@ -0,0 +1,18 @@
import React from "react";
import "react-app-polyfill/stable";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -0,0 +1,142 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { MultiLang } from "../config";
import BrainCoordinateUtil, { BrainCoordinate, ConversionType } from "./lib/BrainCoordinateUtil";
interface Props {
lang: MultiLang;
}
const NimgcenterBrainConv: React.FC<Props> = (props: Props) => {
const defaultInputFormat = "\\d+\\.?\\d*";
const defaultOutputFormat = "%x,%y,%z\\n";
const [inputFormat, setInputFormat] = useState<string>(defaultInputFormat);
const [inputArea, setInputArea] = useState<string>("");
const [outputFormat, setOutputFormat] = useState<string>(defaultOutputFormat);
const [outputArea, setOutputArea] = useState<string>("");
const [conversionType, setConversionType] = useState<ConversionType>("icbm_spm2tal");
const parseInput = (input: string, format: string): BrainCoordinate[] => {
const ret: BrainCoordinate[] = [];
const regObj = new RegExp(format, "g");
const values = input.match(regObj) || [];
for (let i = 0; i + 2 < values.length; i += 3) {
ret.push({ x: parseFloat(values[i]), y: parseFloat(values[i + 1]), z: parseFloat(values[i + 2]) });
}
return ret;
};
const formatNumber = (value: number): string => {
const digit = 4;
const width = 7;
const ret = value.toFixed(digit);
return ret.length < width ? " ".repeat(width - ret.length) + ret : ret;
};
const onChangeInputFormat = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputFormat(event.target.value);
};
const onClickDefaultInputFormatButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setInputFormat(defaultInputFormat);
};
const onChangeInputArea = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setInputArea(event.target.value);
};
const onChangeOutputFormat = (event: React.ChangeEvent<HTMLInputElement>) => {
setOutputFormat(event.target.value);
};
const onClickDefaultOutputFormatButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setOutputFormat(defaultOutputFormat);
};
const onChangeMatrixType = (event: React.ChangeEvent<HTMLSelectElement>) => {
if (BrainCoordinateUtil.isConversionType(event.target.value)) {
setConversionType(event.target.value as ConversionType);
}
};
const onClickClearButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setInputArea("");
setOutputArea("");
};
const onClickConvertButton = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
const inputValues = parseInput(inputArea, inputFormat);
const outputValues = inputValues.map((inputValue) => BrainCoordinateUtil.convertCoordinate(conversionType, inputValue));
const outputTextArea = outputValues
.map((value) => {
return outputFormat.replace("%x", formatNumber(value.x)).replace("%y", formatNumber(value.y)).replace("%z", formatNumber(value.z)).replace("\\n", "\n");
})
.join("");
setOutputArea(outputTextArea);
};
const matrix = BrainCoordinateUtil.getConversionMatrix(conversionType);
const matrixArea = "Conversion Matrix:\n" + matrix.map((vect) => " " + vect.map((value) => formatNumber(value)).join(", ")).join("\n");
return (
<div style={{ textAlign: "center" }}>
<div style={{ background: "navajowhite", padding: "3px 0" }}>
Brain Coordinate Converter is able to convert list of brain coordinate. <Link to="./howtouse.php">How To Use</Link>
</div>
<table style={{ textAlign: "left" }}>
<tbody>
<tr>
<td>
<b>Input format: (Regular expression).</b>
<br />
<input type="text" value={inputFormat} onChange={onChangeInputFormat} />
<input type="button" onClick={onClickDefaultInputFormatButton} value="Default" />
</td>
<td>
<b>Output format: (%x,%y,%z is replaced. and \n)</b>
<br />
<input type="text" value={outputFormat} onChange={onChangeOutputFormat} />
<input type="button" onClick={onClickDefaultOutputFormatButton} value="Default" />
</td>
</tr>
<tr>
<td>
<b>Input:</b>
<br />
<textarea cols={37} rows={10} wrap="off" value={inputArea} onChange={onChangeInputArea} />
</td>
<td>
<b>Results:</b>
<br />
<textarea cols={37} rows={10} wrap="off" value={outputArea} readOnly />
</td>
</tr>
<tr>
<td>
<select size={8} onChange={onChangeMatrixType} value={conversionType}>
{BrainCoordinateUtil.getConversionTypes().map((type) => (
<option key={type} value={type}>
{BrainCoordinateUtil.getConversionLabel(type)}
</option>
))}
</select>
</td>
<td>
<textarea cols={37} rows={5} wrap="off" value={matrixArea} readOnly />
</td>
</tr>
<tr>
<td>
<input type="button" onClick={onClickConvertButton} value="Convert" />
</td>
<td>
<input type="button" onClick={onClickClearButton} value="Clear" />
</td>
</tr>
</tbody>
</table>
</div>
);
};
export default NimgcenterBrainConv;

View File

@@ -0,0 +1,34 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Navigate, Route, Routes } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import jsonHowToUse from "./assets/howtouse.json";
import jsonUtilities from "./assets/utilities.json";
import NimgcenterBrainConv from "./NimgcenterBrainConv";
interface Props {
lang: MultiLang;
}
const NimgcenterUtilities: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<>
<Helmet>
<title>NIMG Utilities</title>
</Helmet>
<Routes>
<Route path="" element={<XoopsCode lang={lang} text={jsonUtilities[lang]} dohtml={true} />} />
<Route path="brain_conv/" element={<NimgcenterBrainConv lang={lang} />} />
<Route path="brain_conv/howtouse.php" element={<XoopsCode lang={lang} text={jsonHowToUse[lang]} dohtml={true} />} />
<Route path="index.php" element={<Navigate to="/modules/xnpnimgcenter/utilities/" />} />
<Route path="brain_conv/index.php" element={<Navigate to="/modules/xnpnimgcenter/utilities/brain_conv/" />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
</>
);
};
export default NimgcenterUtilities;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"en":"<h1>\n Utilities\n<\/h1><br \/>\n<br \/>\n<table class=\"outer\">\n <tbody>\n <tr>\n <td width=\"10%\" style=\"vertical-align:middle; text-align:center;\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\"><img src=\"\/modules\/xnpnimgcenter\/utilities\/images\/brain_conv_ss.png\" alt=\"brainconv\" \/><\/a>\n <\/td>\n <td width=\"90%\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\">Brain Coordinate Converter<\/a><br \/>\n This utility is able to mutually convert several types of brain coordinates(Requires JavaScript).<br \/>\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/howtouse.php\">How to use<\/a>\n <\/td>\n <\/tr>\n <\/tbody>\n<\/table>","ja":"<h1>\n ユーティリティ\n<\/h1><br \/>\n<br \/>\n<table class=\"outer\">\n <tbody>\n <tr>\n <td width=\"10%\" style=\"vertical-align:middle; text-align:center;\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\"><img src=\"\/modules\/xnpnimgcenter\/utilities\/images\/brain_conv_ss.png\" alt=\"brainconv\" \/><\/a>\n <\/td>\n <td width=\"90%\">\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/\">脳座標変換ツール<\/a><br \/>\n 数種類の脳座間の変換が行えます要Javascript。<br \/>\n <a href=\"\/modules\/xnpnimgcenter\/utilities\/brain_conv\/howtouse.php\">使い方<\/a>\n <\/td>\n <\/tr>\n <\/tbody>\n<\/table>"}

View File

@@ -0,0 +1,152 @@
export interface BrainCoordinate {
x: number;
y: number;
z: number;
}
export type ConversionType = "icbm_spm2tal" | "tal2icbm_spm" | "icbm_fsl2tal" | "tal2icbm_fsl" | "icbm_other2tal" | "tal2icbm_other";
type ConversionVector = readonly [number, number, number, number];
type ConversionMatrix = readonly [ConversionVector, ConversionVector, ConversionVector, ConversionVector];
interface ConversionMatrixData {
[key: string]: {
label: string;
matrix: ConversionMatrix;
};
}
const conversionMatrix: ConversionMatrixData = {
icbm_spm2tal: {
label: "icbm_spm => Talairach",
matrix: [
[0.9254, 0.0024, -0.0118, -1.0207],
[-0.0048, 0.9316, -0.0871, -1.7667],
[0.0152, 0.0883, 0.8924, 4.0926],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_spm: {
label: "Talairach => icbm_spm",
matrix: [
[1.0804, -0.0041, 0.0139, 1.0387],
[0.0038, 1.0636, 0.1039, 1.4579],
[-0.0188, -0.1052, 1.1101, -4.748],
[0.0, 0.0, 0.0, 1.0],
],
},
icbm_fsl2tal: {
label: "icbm_fsl => Talairach",
matrix: [
[0.9464, 0.0034, -0.0026, -1.068],
[-0.0083, 0.9479, -0.058, -1.0239],
[0.0053, 0.0617, 0.901, 3.1883],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_fsl: {
label: "Talairach => icbm_fsl",
matrix: [
[1.0566, -0.004, 0.0028, 1.1155],
[0.0088, 1.0505, 0.0677, 0.8694],
[-0.0068, -0.0719, 1.1052, -3.6047],
[0.0, 0.0, 0.0, 1.0],
],
},
icbm_other2tal: {
label: "other(SPM99,Brain Voyager) => Talairach",
matrix: [
[0.9357, 0.0029, -0.0072, -1.0423],
[-0.0065, 0.9396, -0.0726, -1.394],
[0.0103, 0.0752, 0.8967, 3.6475],
[0.0, 0.0, 0.0, 1.0],
],
},
tal2icbm_other: {
label: "Talairach => other(SPM99,Brain Voyager)",
matrix: [
[1.0686, -0.004, 0.0083, 1.0782],
[0.0064, 1.0574, 0.0857, 1.1682],
[-0.0128, -0.0886, 1.1079, -4.178],
[0.0, 0.0, 0.0, 1.0],
],
},
/*
tal2mni: {
label: 'Talairach => MNI(Matthew Brett,99)',
matrix: null // See also, http://eeg.sourceforge.net/doc_m2html/bioelectromagnetism/tal2mni.html
},
mni2tal: {
label: 'MNI => Talairach(Matthew Brett,99)',
matrix: null // See also, http://eeg.sourceforge.net/doc_m2html/bioelectromagnetism/mni2tal.html
}
*/
};
const isConversionType = (type: string): boolean => {
return type in conversionMatrix;
};
const getConversionTypes = (): ConversionType[] => {
return Object.keys(conversionMatrix) as ConversionType[];
};
const getConversionLabel = (type: ConversionType): string => {
return conversionMatrix[type].label;
};
const getConversionMatrix = (type: ConversionType): ConversionMatrix => {
return conversionMatrix[type].matrix;
};
const mulMatrix = (mat: ConversionMatrix, vec: ConversionVector): ConversionVector => {
return [mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3], mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3], mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3], mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3]];
};
const convertCoordinate = (type: ConversionType, coord: BrainCoordinate): BrainCoordinate => {
const vec: ConversionVector = [coord.x, coord.y, coord.z, 1.0];
const res = mulMatrix(conversionMatrix[type].matrix, vec);
return { x: res[0], y: res[1], z: res[2] };
};
/*
function mni2tal(v) {
if (v[2] < 0) {
return new Array(
0.9900 * v[0],
0.9688 * v[1] + 0.0420 * v[2],
-0.0485 * v[1] + 0.8390 * v[2]
);
} else {
return new Array(
0.9900 * v[0],
0.9688 * v[1] + 0.0460 * v[2],
-0.0485 * v[1] + 0.9189 * v[2]
);
}
}
function tal2mni(v) {
if (v[2] < 0) {
return new Array(
1.0101 * v[0],
0.9688 * v[1] + 0.0460 * v[2],
-0.0485 * v[1] + 0.9189 * v[2]
);
} else {
return new Array(
1.0101 * v[0],
0.9712 * v[1] + 0.0501 * v[2],
-0.0485 * v[1] + 0.8390 * v[2]
);
}
}
*/
const BrainCoordinateUtil = {
isConversionType,
getConversionTypes,
getConversionLabel,
getConversionMatrix,
convertCoordinate,
};
export default BrainCoordinateUtil;

65
src/nimgdocs/Nimgdocs.tsx Normal file
View File

@@ -0,0 +1,65 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import PageNotFound from "../common/lib/PageNotFound";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import Functions from "../functions";
import json3DBrowse from "./assets/3dbrowse.json";
import json3DBrowseUsage from "./assets/3dbrowse_usage.json";
import jsonAbout from "./assets/about.json";
import jsonAdditem from "./assets/additem.json";
import jsonIntroduction from "./assets/introduction.json";
import jsonNimgcenter from "./assets/nimgcenter.json";
import jsonNimgsearch from "./assets/nimgsearch.json";
import NimgdocsBulletinLog from "./NimgdocsBulletinLog";
import NimgdocsTutorilas from "./NimgdocsTutorials";
interface Props {
lang: MultiLang;
}
const jsonDocuments = [
{ name: "about_NIMGPF", data: jsonAbout },
{ name: "additem", data: jsonAdditem },
{ name: "introduction", data: jsonIntroduction },
{ name: "nimgcenter", data: jsonNimgcenter },
{ name: "nimgsearch", data: jsonNimgsearch },
{ name: "3DBrowse", data: json3DBrowse },
{ name: "3DBrowse_usage", data: json3DBrowseUsage },
];
const NimgdocsPage: React.FC<Props> = (props: Props) => {
const { lang } = props;
const location = useLocation();
const params = new URLSearchParams(location.search);
const docname = params.get("docname") || "";
const doc = jsonDocuments.find((value) => value.name === docname);
if (typeof doc !== "undefined") {
return (
<>
<Helmet>
<title>NIMG Documents - {Functions.siteTitle(lang)}</title>
</Helmet>
<XoopsCode lang={lang} text={doc.data[lang]} dohtml={true} />
</>
);
}
return <PageNotFound lang={lang} />;
};
const Nimgdocs: React.FC<Props> = (props: Props) => {
const { lang } = props;
return (
<Routes>
<Route path="" element={<Navigate to="/modules/nimgdocs/index.php?docname=about_NIMGPF" />} />
<Route path="index.php" element={<NimgdocsPage lang={lang} />} />
<Route path="tutorials/index.php" element={<Navigate to="/modules/nimgdocs/tutorials/" />} />
<Route path="tutorials/" element={<NimgdocsTutorilas lang={lang} />} />
<Route path="bulletin/log.php" element={<NimgdocsBulletinLog lang={lang} />} />
<Route path="*" element={<PageNotFound lang={lang} />} />
</Routes>
);
};
export default Nimgdocs;

View File

@@ -0,0 +1,45 @@
import React from "react";
import { Helmet } from "react-helmet-async";
import XoopsCode from "../common/lib/XoopsCode";
import { MultiLang } from "../config";
import Functions from "../functions";
import jsonBulletin from "./assets/bulletin.json";
interface Props {
lang: MultiLang;
}
const NimgdocsBulletinLog: React.FC<Props> = (props: Props) => {
const { lang } = props;
const title = Functions.mlang("[en]Expired Posts[/en][ja]掲載期間の終了した投稿[/ja]", lang);
const now = new Date().getTime() / 1000;
const logs = jsonBulletin.filter((log) => log.end_date < now && log.text[lang] !== "");
return (
<>
<Helmet>
<title>
{title} - NIMG Documents - {Functions.siteTitle(lang)}
</title>
</Helmet>
<h1>{Functions.mlang("[en]Expired Posts[/en][ja]掲載期間の終了した投稿[/ja]", lang)}</h1>
{logs.map((log) => {
return (
<div key={log.id}>
{log.tags.map((tag) => (
<span key={tag} style={{ margin: "0 2px", padding: "0 2px", color: "#666", backgroundColor: "#eee" }}>
{tag}
</span>
))}
<XoopsCode lang={lang} text={log.text[lang]} dobr />
<div style={{ margin: "5px 0", textAlign: "right" }}>
{Functions.formatDate(log.post_date, "YYYY-MM-DD HH:mm:ss")} Posted by <b>{log.author}</b>
</div>
<hr style={{ margin: "10px 0", border: "none", borderTop: "1px dotted #ccc" }} />
</div>
);
})}
</>
);
};
export default NimgdocsBulletinLog;

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