update required libraries.
@@ -3,30 +3,43 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@types/jest": "24.0.13",
|
||||
"@types/async-lock": "^1.1.1",
|
||||
"@types/jest": "24.0.22",
|
||||
"@types/jsonp": "^0.2.0",
|
||||
"@types/lokijs": "^1.5.2",
|
||||
"@types/node": "12.0.2",
|
||||
"@types/rc-tree": "^1.11.3",
|
||||
"@types/react": "16.8.18",
|
||||
"@types/react-dom": "16.8.4",
|
||||
"@types/react-helmet": "^5.0.8",
|
||||
"@types/react-overlays": "^1.1.2",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"axios": "^0.18.0",
|
||||
"@types/node": "12.12.6",
|
||||
"@types/pako": "^1.0.1",
|
||||
"@types/react": "16.9.11",
|
||||
"@types/react-dom": "16.9.3",
|
||||
"@types/react-helmet": "^5.0.14",
|
||||
"@types/react-html-parser": "^2.0.1",
|
||||
"@types/react-overlays": "^1.1.3",
|
||||
"@types/react-router-dom": "^5.1.2",
|
||||
"@types/react-router-hash-link": "^1.2.1",
|
||||
"@types/xregexp": "^3.0.30",
|
||||
"async-lock": "^1.2.2",
|
||||
"axios": "^0.19.0",
|
||||
"jsonp": "^0.2.1",
|
||||
"lokijs": "^1.5.6",
|
||||
"rc-tree": "^2.0.0",
|
||||
"react": "^16.8.6",
|
||||
"react-app-polyfill": "^1.0.1",
|
||||
"react-dom": "^16.8.6",
|
||||
"lokijs": "^1.5.8",
|
||||
"moment": "^2.24.0",
|
||||
"pako": "^1.0.10",
|
||||
"rc-tree": "3.0.0-alpha.37",
|
||||
"react": "^16.11.0",
|
||||
"react-app-polyfill": "^1.0.4",
|
||||
"react-cookie": "^4.0.1",
|
||||
"react-dom": "^16.11.0",
|
||||
"react-ga": "^2.7.0",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-html-parser": "^2.0.2",
|
||||
"react-image-lightbox": "^5.1.0",
|
||||
"react-overlays": "^1.2.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-spinner-material": "^1.1.1",
|
||||
"react-transition-group": "^4.0.1",
|
||||
"typescript": "3.4.5"
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-hash-link": "^1.2.2",
|
||||
"react-scripts": "3.2.0",
|
||||
"react-spinner-material": "^1.1.3",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"typescript": "3.7.2",
|
||||
"xregexp": "^4.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import AppRoot from './components/AppRoot';
|
||||
import AppRoot from './common/AppRoot';
|
||||
import './App.css';
|
||||
|
||||
class App extends Component {
|
||||
|
||||
|
Before Width: | Height: | Size: 627 B |
|
Before Width: | Height: | Size: 619 B |
|
Before Width: | Height: | Size: 64 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 592 B |
@@ -0,0 +1,81 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ReactCookieProps, withCookies } from 'react-cookie';
|
||||
import ReactGA from 'react-ga';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import Config, { MultiLang } from '../config';
|
||||
import Functions from '../functions';
|
||||
import Container from './Container';
|
||||
import Footer from './Footer';
|
||||
import Header from './Header';
|
||||
|
||||
|
||||
interface Props extends RouteComponentProps, ReactCookieProps { }
|
||||
interface State {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
class AppRoot extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { lang: 'en' };
|
||||
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') {
|
||||
ReactGA.initialize(Config.GOOGLE_ANALYTICS_TRACKING_ID);
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
const params = new URLSearchParams(nextProps.location.search);
|
||||
const param_lang = params.get('ml_lang');
|
||||
const cookie_lang = typeof nextProps.cookies !== 'undefined' ? nextProps.cookies.get('ml_lang') : null;
|
||||
let lang = param_lang || (typeof cookie_lang === 'string' ? cookie_lang : null) || prevState.lang;
|
||||
if (lang !== 'en' && lang !== 'ja') {
|
||||
lang = 'en';
|
||||
}
|
||||
if (cookie_lang !== lang) {
|
||||
if (typeof nextProps.cookies !== 'undefined') {
|
||||
nextProps.cookies.set('ml_lang', lang, { path: '/' });
|
||||
}
|
||||
}
|
||||
if (prevState.lang !== lang) {
|
||||
return { lang };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { pathname } = this.props.location;
|
||||
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') {
|
||||
ReactGA.set({ page: pathname });
|
||||
ReactGA.pageview(pathname);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { pathname, search } = this.props.location;
|
||||
if (Config.GOOGLE_ANALYTICS_TRACKING_ID !== '') {
|
||||
ReactGA.set({ page: pathname });
|
||||
ReactGA.pageview(pathname);
|
||||
}
|
||||
if (pathname !== prevProps.location.pathname || search !== prevProps.location.search) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = this.state;
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{Functions.siteTitle(lang)} - {Functions.siteSlogan(lang)}</title>
|
||||
</Helmet>
|
||||
<Header lang={lang} />
|
||||
<Container lang={lang} />
|
||||
<Footer lang={lang} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withCookies(withRouter(AppRoot));
|
||||
@@ -1,8 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route, Link } from 'react-router-dom';
|
||||
import DatabaseTop from './database/DatabaseTop';
|
||||
import MainContent from './MainContent';
|
||||
import { Link, Route, Switch } from 'react-router-dom';
|
||||
import { MultiLang } from '../config';
|
||||
import DatabaseTop from '../database/DatabaseTop';
|
||||
import styles from './CenterColumn.module.css';
|
||||
import MainContent from './MainContent';
|
||||
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const Database = () => {
|
||||
return (<> » Database</>);
|
||||
@@ -23,7 +28,8 @@ const BreadClumbs = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const CenterBlocks = () => {
|
||||
const CenterBlocks = (props: Props) => {
|
||||
const { lang } = props;
|
||||
return (
|
||||
<table className={styles.centerBlocks}>
|
||||
<tbody>
|
||||
@@ -31,7 +37,7 @@ const CenterBlocks = () => {
|
||||
<td colSpan={2}>
|
||||
<h2 className={styles.centerMainTitle}>Registered Itemtypes</h2>
|
||||
<div className={styles.centerMainContent}>
|
||||
<DatabaseTop />
|
||||
<DatabaseTop lang={lang} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -46,14 +52,16 @@ const CenterBlocks = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const CenterColumn = () => {
|
||||
const CenterColumn = (props: Props) => {
|
||||
const { lang } = props;
|
||||
|
||||
return (
|
||||
<td className={styles.centerColumn}>
|
||||
<BreadClumbs />
|
||||
<Switch>
|
||||
<Route exact path="/" component={CenterBlocks} />
|
||||
<Route exact path="/" render={() => <CenterBlocks lang={lang} />} />
|
||||
</Switch>
|
||||
<MainContent />
|
||||
<MainContent lang={lang} />
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,24 @@
|
||||
import React from 'react';
|
||||
import LeftColumn from './LeftColumn';
|
||||
import { MultiLang } from '../config';
|
||||
import CenterColumn from './CenterColumn';
|
||||
import RightColumn from './RightColumn';
|
||||
import styles from './Container.module.css';
|
||||
import LeftColumn from './LeftColumn';
|
||||
import RightColumn from './RightColumn';
|
||||
|
||||
const Container = () => {
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const Container = (props: Props) => {
|
||||
const { lang } = props;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<table className={styles.wrapper}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<LeftColumn />
|
||||
<CenterColumn />
|
||||
<RightColumn />
|
||||
<LeftColumn lang={lang} />
|
||||
<CenterColumn lang={lang} />
|
||||
<RightColumn lang={lang} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -1,6 +1,6 @@
|
||||
.footer {
|
||||
height: 30px;
|
||||
background: url(../assets/images/theme/footer_back.jpg) top right repeat-x;
|
||||
background: url(./assets/images/theme/footer_back.jpg) top right repeat-x;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import React from 'react';
|
||||
import { MultiLang } from '../config';
|
||||
import styles from './Footer.module.css';
|
||||
|
||||
const Footer = () => {
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const Footer = (props: Props) => {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<div className={styles.copyright}>
|
||||
@@ -1,12 +1,12 @@
|
||||
.header {
|
||||
padding-bottom: 15px;
|
||||
background: url(../assets/images/theme/header_bar.jpg) bottom left repeat-x;
|
||||
background: url(./assets/images/theme/header_bar.jpg) bottom left repeat-x;
|
||||
}
|
||||
|
||||
.h1 {
|
||||
min-width: 900px;
|
||||
height: 116px;
|
||||
background: url(../assets/images/theme/pupil_photo.jpg) top right no-repeat;
|
||||
background: url(./assets/images/theme/pupil_photo.jpg) top right no-repeat;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MultiLang } from '../config';
|
||||
import logo from './assets/images/theme/pupil_logo.jpg';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const Header = (props: Props) => {
|
||||
return <header className={styles.header}>
|
||||
<h1 className={styles.h1}><Link to="/"><img className={styles.img} src={logo} alt="Pupil platform" title="Communication platform for pupil researchers" /></Link></h1>
|
||||
</header>;
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -9,14 +9,15 @@
|
||||
|
||||
.leftBlockTitle {
|
||||
padding: 4px 10px 4px 22px;
|
||||
background: url(../assets/images/theme/left_title.jpg) top left no-repeat;
|
||||
background: url(./assets/images/theme/left_title.jpg) top left no-repeat;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.leftBlockContent {
|
||||
padding: 10px 5px 20px;
|
||||
background: #cae5ff url(../assets/images/theme/left_back.jpg) top left repeat-x;
|
||||
width: 190px;
|
||||
background: #cae5ff url(./assets/images/theme/left_back.jpg) top left repeat-x;
|
||||
font-size: 70%;
|
||||
line-height: 120%;
|
||||
color: #666;
|
||||
@@ -1,28 +1,34 @@
|
||||
import React from 'react';
|
||||
import { MultiLang } from '../config';
|
||||
import IndexTree from '../database/blocks/IndexTree';
|
||||
import Search from '../database/blocks/Search';
|
||||
import MainMenu from './blocks/MainMenu';
|
||||
import Search from './blocks/Search';
|
||||
import IndexTree from './blocks/IndexTree';
|
||||
import styles from './LeftColumn.module.css';
|
||||
|
||||
const LeftColumn = () => {
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const LeftColumn = (props: Props) => {
|
||||
const { lang } = props;
|
||||
return (
|
||||
<td className={styles.leftColumn}>
|
||||
<div className={styles.leftBlock}>
|
||||
<h2 className={styles.leftBlockTitle}>Main Menu</h2>
|
||||
<div className={styles.leftBlockContent}>
|
||||
<MainMenu />
|
||||
<MainMenu lang={lang} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.leftBlock}>
|
||||
<h2 className={styles.leftBlockTitle}>Search</h2>
|
||||
<div className={styles.leftBlockContent}>
|
||||
<Search />
|
||||
<Search lang={lang} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.leftBlock}>
|
||||
<h2 className={styles.leftBlockTitle}>Index Tree</h2>
|
||||
<div className={styles.leftBlockContent}>
|
||||
<IndexTree />
|
||||
<IndexTree lang={lang} />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import Database from '../database/Database';
|
||||
import DatabaseXoopsPathRedirect from '../database/DatabaseXoopsPathRedirect';
|
||||
import About from './About';
|
||||
import styles from './MainContent.module.css';
|
||||
import XoopsPathRedirect from './XoopsPathRedirect';
|
||||
import { MultiLang } from '../config';
|
||||
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const MainContent = (props: Props) => {
|
||||
const { lang } = props;
|
||||
return (
|
||||
<div className={styles.mainContent}>
|
||||
<Switch>
|
||||
<Route path="/database" render={() => <Database lang={lang} />} />
|
||||
<Route exact path="/about" component={About} />
|
||||
<Route path="/modules/xoonips" render={() => <DatabaseXoopsPathRedirect lang={lang} />} />
|
||||
<Route component={XoopsPathRedirect} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainContent;
|
||||
@@ -7,7 +7,7 @@
|
||||
margin-bottom: 15px;
|
||||
padding: 5px 10px 20px;
|
||||
border: 1px solid #ccc;
|
||||
background: #edf3fc url(../assets/images/theme/right_back.jpg) top left repeat-x;
|
||||
background: #edf3fc url(./assets/images/theme/right_back.jpg) top left repeat-x;
|
||||
}
|
||||
|
||||
.rightBlockTitle {
|
||||
@@ -1,18 +1,24 @@
|
||||
import React from 'react';
|
||||
import FlickrBadge from './blocks/FlickrBadge';
|
||||
import { MultiLang } from '../config';
|
||||
import FlickrBadge from '../custom/blocks/FlickrBadge';
|
||||
import styles from './RightColumn.module.css';
|
||||
|
||||
const RightColumn = () => {
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const RightColumn = (props: Props) => {
|
||||
const { lang } = props;
|
||||
return (
|
||||
<td className={styles.rightColumn}>
|
||||
<div className={styles.rightBlock}>
|
||||
<h2 className={styles.rightBlockTitle}>Eye Photos</h2>
|
||||
<div className={styles.rightBlockContent}>
|
||||
<FlickrBadge tags="eye,pupil" />
|
||||
<FlickrBadge tags="eye,pupil" lang={lang} />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
export default RightColumn;
|
||||
export default RightColumn;
|
||||
@@ -0,0 +1,44 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router';
|
||||
import { MultiLang } from '../config';
|
||||
import PageNotFound from './lib/PageNotFound';
|
||||
|
||||
interface Params {
|
||||
module: string;
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<Params> {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
class XoopsPathRedirect extends Component<Props> {
|
||||
|
||||
getRedirectUrl() {
|
||||
const { pathname } = this.props.location;
|
||||
switch (pathname || '') {
|
||||
case '/index.php': {
|
||||
return '/';
|
||||
}
|
||||
case '/modules/mailform':
|
||||
case '/modules/mailform/index.php': {
|
||||
return '/about';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = this.props;
|
||||
if (this.props.location.pathname === '/') {
|
||||
return null;
|
||||
}
|
||||
const url = this.getRedirectUrl();
|
||||
if (url === '') {
|
||||
return <PageNotFound lang={lang} />;
|
||||
}
|
||||
return <Redirect to={url} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default XoopsPathRedirect;
|
||||
|
Before Width: | Height: | Size: 121 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 121 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 121 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 352 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 468 B |
@@ -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;}
|
||||
@@ -1,8 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MultiLang } from '../../config';
|
||||
import styles from './MainMenu.module.css';
|
||||
|
||||
const MainMenu = () => {
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const MainMenu = (props: Props) => {
|
||||
return (
|
||||
<ul className={styles.mainmenu}>
|
||||
<li><Link className={styles.menuTop} to="/">Home</Link></li>
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { Link } 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 extends RouteComponentProps {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const styleLink = {
|
||||
fontSize: '8px',
|
||||
};
|
||||
const styleImage = {
|
||||
verticalAlign: 'middle',
|
||||
border: '1px solid #000',
|
||||
};
|
||||
const langResources = {
|
||||
en: { image: mlangEnglish, title: 'English' },
|
||||
ja: { image: mlangJapanese, title: 'Japanese' },
|
||||
};
|
||||
|
||||
const LangFlag = (props: Props) => {
|
||||
const { lang } = props;
|
||||
const params = new URLSearchParams(props.location.search);
|
||||
const flagLang = lang === 'en' ? 'ja' : 'en';
|
||||
params.set('ml_lang', flagLang);
|
||||
const url = props.location.pathname + '?' + params.toString();
|
||||
return (
|
||||
<Link style={styleLink} to={url}>
|
||||
<img style={styleImage} src={langResources[flagLang].image} alt={langResources[flagLang].title} title={langResources[flagLang].title}/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default withRouter(LangFlag);
|
||||
@@ -0,0 +1,58 @@
|
||||
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;
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import Spinner from 'react-spinner-material';
|
||||
import styles from './Loading.module.css';
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<div className={styles.loading}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignContent: 'center', margin: '100px 0 0 0' }}>
|
||||
<Spinner size={70} spinnerColor={'#cccccc'} spinnerWidth={8} visible={true} />
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { MultiLang } from '../../config';
|
||||
import Functions from '../../functions';
|
||||
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const NoticeSiteHasBeenArchived = (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;
|
||||
@@ -1,27 +1,39 @@
|
||||
import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { MultiLang } from '../../config';
|
||||
import Functions from '../../functions';
|
||||
|
||||
interface Props extends RouteComponentProps { }
|
||||
interface Props extends RouteComponentProps {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
class PageNotFound extends Component<Props> {
|
||||
|
||||
private url = '/';
|
||||
private timer: NodeJS.Timer | null = null;
|
||||
|
||||
goToTopPage() {
|
||||
if (this.props.location.pathname !== '/') {
|
||||
setTimeout(() => {
|
||||
this.timer = setTimeout(() => {
|
||||
this.props.history.push(this.url);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = this.props;
|
||||
this.goToTopPage();
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>Page Not Found - Pupil Platform</title>
|
||||
<title>Page Not Found - {Functions.siteTitle(lang)}</title>
|
||||
</Helmet>
|
||||
<h1>Page Not Found</h1>
|
||||
<section>
|
||||
@@ -0,0 +1,45 @@
|
||||
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 = (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;
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import ReactHtmlParser, { convertNodeToElement } from 'react-html-parser';
|
||||
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\]/sg, (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\]/sg, (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 />');
|
||||
}
|
||||
|
||||
interface TransformParsedNode {
|
||||
type: string;
|
||||
next: object | null;
|
||||
prev: object | null;
|
||||
parent: object | null;
|
||||
name: string;
|
||||
attribs: any;
|
||||
children: object[];
|
||||
data: string;
|
||||
}
|
||||
|
||||
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 transform = (node: object, idx: number): ReactElement | null | void => {
|
||||
const node_ = node as TransformParsedNode;
|
||||
if (node_.type === 'tag' && node_.name === 'a') {
|
||||
const url = node_.attribs && node_.attribs['href'];
|
||||
const isExternal = !url || /^(mailto|https?:?\/\/)/.test(url);
|
||||
if (!isExternal) {
|
||||
const style = (node_.attribs && node_.attribs['style']) || '';
|
||||
const title = (node_.attribs && node_.attribs['title']) || '';
|
||||
return <HashLink key={idx} to={url} style={cssConvert(style)} title={title}>{node_.children.map((value: object, index: number) => {
|
||||
return convertNodeToElement(value, index, transform);
|
||||
})}</HashLink>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const XoopsCode = (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 })}</div>;
|
||||
}
|
||||
|
||||
export default XoopsCode;
|
||||
@@ -1,33 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import Header from './Header';
|
||||
import Container from './Container';
|
||||
import Footer from './Footer';
|
||||
|
||||
interface Props extends RouteComponentProps { }
|
||||
interface State { }
|
||||
|
||||
class AppRoot extends Component<Props, State> {
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (this.props.location !== prevProps.location) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Pupil Platform</title>
|
||||
</Helmet>
|
||||
<Header />
|
||||
<Container />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(AppRoot);
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './Header.module.css';
|
||||
import logo from '../assets/images/theme/pupil_logo.jpg';
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<h1 className={styles.h1}><Link to="/"><img className={styles.img} src={logo} alt="Pupil platform" title="Communication platform for pupil researchers" /></Link></h1>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -1,6 +0,0 @@
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
margin: 100px 0 0 0;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import Database from './database/Database';
|
||||
import About from './About';
|
||||
import XoopsPathRedirect from './XoopsPathRedirect';
|
||||
import styles from './MainContent.module.css';
|
||||
|
||||
const MainContent = () => {
|
||||
return (
|
||||
<div className={styles.mainContent}>
|
||||
<Switch>
|
||||
<Route path="/database" component={Database} />
|
||||
<Route exact path="/about" component={About} />
|
||||
<Route component={XoopsPathRedirect} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainContent;
|
||||
@@ -1,63 +0,0 @@
|
||||
.indexTree {
|
||||
border-top: 1px solid #9ab5cf;
|
||||
border-left: 1px solid #9ab5cf;
|
||||
border-bottom: 1px solid #404040;
|
||||
border-right: 1px solid #404040;
|
||||
background-color: white;
|
||||
height: 250px;
|
||||
width: 175px;
|
||||
overflow: auto;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.formButton {
|
||||
margin: 3px 3px 10px;
|
||||
}
|
||||
|
||||
.indexTree li span {
|
||||
color: blue;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.indexTree li span:hover {
|
||||
color: #ff9900;
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li ul) {
|
||||
padding: 0 0 0 9px;
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li span.rc-tree-switcher) {
|
||||
height: 19px;
|
||||
width: 21px;
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree > li:first-child > span.rc-tree-switcher.rc-tree-switcher_open), .indexTree:global(.rc-tree > li:first-child > span.rc-tree-switcher.rc-tree-switcher_close) {
|
||||
background: url(../../assets/images/theme/tree-root.gif);
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree > li:first-child > ul) {
|
||||
padding: 0 0 0 6px;
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li:not(:last-child) ul.rc-tree-child-tree.rc-tree-child-tree-open) {
|
||||
background: url(../../assets/images/theme/tree-line.gif) repeat-y;
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li:not(:last-child) > span.rc-tree-switcher-noop) {
|
||||
background: url(../../assets/images/theme/tree-leaf1.gif)
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li:last-child > span.rc-tree-switcher-noop) {
|
||||
background: url(../../assets/images/theme/tree-leaf2.gif)
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li:not(:last-child) > span.rc-tree-switcher_open), .indexTree:global(.rc-tree li:not(:last-child) > span.rc-tree-switcher_close) {
|
||||
background: url(../../assets/images/theme/tree-parent1.gif)
|
||||
}
|
||||
|
||||
.indexTree:global(.rc-tree li:last-child > span.rc-tree-switcher_open), .indexTree:global(.rc-tree li:last-child > span.rc-tree-switcher_close) {
|
||||
background: url(../../assets/images/theme/tree-parent2.gif)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import React, { Component, FormEvent, ChangeEvent } from 'react';
|
||||
import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import ItemUtil, { SearchByKeywordType } from '../database/lib/ItemUtil';
|
||||
import styles from './Search.module.css';
|
||||
|
||||
interface Props extends RouteComponentProps { }
|
||||
|
||||
interface State {
|
||||
type: SearchByKeywordType;
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
class Search extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(this.props.location.search);
|
||||
this.state = { type, keyword };
|
||||
this.handleChangeType = this.handleChangeType.bind(this);
|
||||
this.handleChangeKeyword = this.handleChangeKeyword.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(nextProps.location.search);
|
||||
this.setState({ type, keyword });
|
||||
}
|
||||
|
||||
handleChangeKeyword(event: ChangeEvent<HTMLInputElement>) {
|
||||
const keyword = event.target.value.trim();
|
||||
this.setState({ keyword });
|
||||
}
|
||||
|
||||
handleChangeType(event: ChangeEvent<HTMLSelectElement>) {
|
||||
const type = event.target.value as SearchByKeywordType;
|
||||
this.setState({ type });
|
||||
}
|
||||
|
||||
handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
const url = ItemUtil.getSearchByKeywordUrl(this.state.type, this.state.keyword);
|
||||
this.props.history.push(url);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form className={styles.searchForm} onSubmit={this.handleSubmit}>
|
||||
<input className={styles.inputKeyword} type="text" value={this.state.keyword} onChange={this.handleChangeKeyword} />
|
||||
|
||||
<select value={this.state.type} onChange={this.handleChangeType}>
|
||||
<option label="ALL" value="all">ALL</option>
|
||||
<option label="Title & Keyword" value="basic">Title & Keyword</option>
|
||||
<option label="Conference" value="conference">Conference</option>
|
||||
<option label="Paper" value="paper">Paper</option>
|
||||
<option label="Book" value="book">Book</option>
|
||||
<option label="Data" value="data">Data</option>
|
||||
<option label="Model" value="model">Model</option>
|
||||
<option label="Url" value="url">Url</option>
|
||||
</select>
|
||||
<br />
|
||||
<input className="formButton" type="submit" value="Search" />
|
||||
|
||||
<Link to="/database/advanced">Advanced</Link>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Search);
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import DatabaseTop from './DatabaseTop';
|
||||
import DatabaseDetailItem from './DatabaseDetailItem';
|
||||
import DatabaseAdvancedSearch from './DatabaseAdvancedSearch';
|
||||
import DatabaseSearchByIndexId from './DatabaseSearchByIndexId';
|
||||
import DatabaseSearchByItemType from './DatabaseSearchByItemType';
|
||||
import DatabaseSearchByKeyword from './DatabaseSearchByKeyword';
|
||||
import DatabaseSearchByAdvancedKeyword from './DatabaseSearchByAdvancedKeyword';
|
||||
import PageNotFound from '../PageNotFound';
|
||||
import styles from './Database.module.css';
|
||||
|
||||
const Database = () => {
|
||||
return (
|
||||
<div className={styles.database}>
|
||||
<Helmet>
|
||||
<title>Database - Pupil Platform</title>
|
||||
</Helmet>
|
||||
<Switch>
|
||||
<Route exact path="/database" component={DatabaseTop} />
|
||||
<Route exact path="/database/item/:id" component={DatabaseDetailItem} />
|
||||
<Route exact path="/database/item/id/:doi" component={DatabaseDetailItem} />
|
||||
<Route exact path="/database/advanced" component={DatabaseAdvancedSearch} />
|
||||
<Route exact path="/database/list" component={DatabaseSearchByIndexId} />
|
||||
<Route exact path="/database/list/:id" component={DatabaseSearchByIndexId} />
|
||||
<Route exact path="/database/search" component={DatabaseSearchByKeyword} />
|
||||
<Route exact path="/database/search/advanced" component={DatabaseSearchByAdvancedKeyword} />
|
||||
<Route exact path="/database/search/itemtype/:itemType" component={DatabaseSearchByItemType} />
|
||||
<Route exact path="/database/search/itemtype/:itemType/:subItemType" component={DatabaseSearchByItemType} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Database;
|
||||
@@ -1,74 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import ItemUtil, { Item } from './lib/ItemUtil';
|
||||
import ItemType from './item-type';
|
||||
import Loading from '../Loading';
|
||||
import PageNotFound from '../PageNotFound';
|
||||
|
||||
interface Props extends RouteComponentProps<{ id: string, doi: string }> { }
|
||||
|
||||
interface State {
|
||||
loading: boolean;
|
||||
item: Item | null;
|
||||
}
|
||||
|
||||
class DatabaseDetailItem extends Component<Props, State> {
|
||||
|
||||
public state: State = {
|
||||
loading: true,
|
||||
item: null,
|
||||
};
|
||||
|
||||
private id: number;
|
||||
private doi: string;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { params } = this.props.match;
|
||||
this.id = typeof params.id !== 'undefined' ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : 0;
|
||||
this.doi = typeof params.doi !== 'undefined' ? params.doi : '';
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { params } = nextProps.match;
|
||||
this.id = typeof params.id !== 'undefined' ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : 0;
|
||||
this.doi = typeof params.doi !== 'undefined' ? params.doi : '';
|
||||
this.updateItem();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateItem();
|
||||
}
|
||||
|
||||
async updateItem() {
|
||||
let item = null;
|
||||
if (this.doi !== '') {
|
||||
item = await ItemUtil.getByDoi(this.doi);
|
||||
} else if (this.id !== 0) {
|
||||
item = await ItemUtil.get(this.id);
|
||||
}
|
||||
this.setState({ loading: false, item });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
if (this.state.item === null) {
|
||||
return <PageNotFound />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{this.state.item.title} - Database - Pupil Platform</title>
|
||||
</Helmet>
|
||||
<h3>Detail</h3>
|
||||
<br />
|
||||
<ItemType.Detail item={this.state.item} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseDetailItem;
|
||||
@@ -1,46 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import AdvancedSearchQuery from './lib/AdvancedSearchQuery';
|
||||
import ItemUtil, { SortCondition } from './lib/ItemUtil';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
|
||||
interface Props extends RouteComponentProps { }
|
||||
|
||||
interface State {
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class DatabaseSearchByAdvancedKeyword extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const query = ItemUtil.getAdvancedSearchQueryByQuery(this.props.location.search);
|
||||
this.state = { query };
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const query = ItemUtil.getAdvancedSearchQueryByQuery(nextProps.location.search);
|
||||
this.setState({ query });
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return ItemUtil.getSearchByAdvancedKeywordsUrl(this.state.query);
|
||||
}
|
||||
|
||||
async search(condition: SortCondition) {
|
||||
return ItemUtil.getListByAdvancedSearchQuery(this.state.query, condition);
|
||||
}
|
||||
|
||||
render() {
|
||||
const baseUrl = this.getUrl();
|
||||
return (
|
||||
<div className="list">
|
||||
<h3>Listing item</h3>
|
||||
<DatabaseListItem url={baseUrl} search={this.search} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByAdvancedKeyword;
|
||||
@@ -1,76 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import IndexUtil, { Index, INDEX_ID_PUBLIC } from './lib/IndexUtil';
|
||||
import ItemUtil, { SortCondition } from './lib/ItemUtil';
|
||||
import DatabaseListIndex from './lib/DatabaseListIndex';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
import PageNotFound from '../PageNotFound';
|
||||
|
||||
interface Props extends RouteComponentProps<{ id: string }> { }
|
||||
|
||||
interface State {
|
||||
index: Index | null;
|
||||
}
|
||||
|
||||
class DatabaseSearchByIndexId extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { params } = this.props.match;
|
||||
const indexId = params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : null) : INDEX_ID_PUBLIC;
|
||||
this.state = {
|
||||
index: indexId ? IndexUtil.get(indexId) : null,
|
||||
}
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { params } = nextProps.match;
|
||||
const indexId = params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : null) : INDEX_ID_PUBLIC;
|
||||
this.setState({
|
||||
index: indexId ? IndexUtil.get(indexId) : null,
|
||||
})
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
if (this.state.index === null) {
|
||||
return '/';
|
||||
}
|
||||
return IndexUtil.getUrl(this.state.index.id);
|
||||
}
|
||||
|
||||
async search(condition: SortCondition) {
|
||||
if (this.state.index === null) {
|
||||
return { total: 0, data: [] };
|
||||
}
|
||||
return ItemUtil.getListByIndexId(this.state.index.id, condition);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.index === null) {
|
||||
return <PageNotFound />;
|
||||
}
|
||||
const baseUrl = this.getUrl();
|
||||
const pIndexes = IndexUtil.getParents(this.state.index.id);
|
||||
const parents = pIndexes.map((value: Index) => {
|
||||
const url: string = IndexUtil.getUrl(value.id);
|
||||
return <Fragment key={value.id}>/ <Link to={url}>{value.title}</Link> </Fragment>;
|
||||
});
|
||||
const title = pIndexes.map((value) => { return '/' + value.title; }).join('');
|
||||
return (
|
||||
<div className="list">
|
||||
<Helmet>
|
||||
<title>{title} - Database - Pupil Platform</title>
|
||||
</Helmet>
|
||||
<h3>Listing item</h3>
|
||||
<div>{parents}</div>
|
||||
<DatabaseListIndex index={this.state.index} />
|
||||
<DatabaseListItem url={baseUrl} search={this.search} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByIndexId;
|
||||
@@ -1,58 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import ItemUtil, { SortCondition } from './lib/ItemUtil';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
|
||||
interface Props extends RouteComponentProps<{ itemType: string, subItemType: string }> { }
|
||||
|
||||
interface State {
|
||||
item_type: string;
|
||||
sub_item_type: string;
|
||||
}
|
||||
|
||||
class DatabaseSearchByItemType extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { params } = this.props.match;
|
||||
this.state = {
|
||||
item_type: params.itemType ? params.itemType : '',
|
||||
sub_item_type: params.subItemType ? params.subItemType : '',
|
||||
};
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { params } = nextProps.match;
|
||||
const item_type = params.itemType ? params.itemType : '';
|
||||
const sub_item_type = params.subItemType ? params.subItemType : '';
|
||||
this.setState({ item_type, sub_item_type });
|
||||
}
|
||||
|
||||
async search(condition: SortCondition) {
|
||||
if (this.state.item_type === '') {
|
||||
return { total: 0, data: [] };
|
||||
}
|
||||
return ItemUtil.getListByItemType(this.state.item_type, this.state.sub_item_type, condition);
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
let url = ItemUtil.getItemTypeSearchUrl(this.state.item_type);
|
||||
if (this.state.sub_item_type !== '') {
|
||||
url += '/' + this.state.sub_item_type;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
render() {
|
||||
const baseUrl = this.getUrl();
|
||||
return (
|
||||
<div className="list">
|
||||
<h3>Listing item</h3>
|
||||
<DatabaseListItem url={baseUrl} search={this.search} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByItemType;
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import ItemUtil, { SearchByKeywordType, SortCondition } from './lib/ItemUtil';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
|
||||
interface Props extends RouteComponentProps { }
|
||||
|
||||
interface State {
|
||||
type: SearchByKeywordType;
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
class DatabaseSearchByKeyword extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(this.props.location.search);
|
||||
this.state = { type, keyword };
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(nextProps.location.search);
|
||||
this.setState({ type, keyword });
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return ItemUtil.getSearchByKeywordUrl(this.state.type, this.state.keyword);
|
||||
}
|
||||
|
||||
async search(condition: SortCondition) {
|
||||
if (this.state.keyword === '') {
|
||||
return { total: 0, data: [] };
|
||||
}
|
||||
return ItemUtil.getListByKeyword(this.state.type, this.state.keyword, condition);
|
||||
}
|
||||
|
||||
render() {
|
||||
const baseUrl = this.getUrl();
|
||||
return (
|
||||
<div className="list">
|
||||
<h3>Listing item</h3>
|
||||
<p>Search Keyword : {this.state.keyword}</p>
|
||||
<DatabaseListItem url={baseUrl} search={this.search} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByKeyword;
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
import ItemType from './item-type';
|
||||
import styles from './DatabaseTop.module.css';
|
||||
|
||||
const DatabaseTop = () => {
|
||||
return (
|
||||
<table className={styles.itemTypes}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={styles.itemType}>
|
||||
<ItemType.Top type="xnpconference" />
|
||||
</td>
|
||||
<td className={styles.itemType}>
|
||||
<ItemType.Top type="xnppaper" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={styles.itemType}>
|
||||
<ItemType.Top type="xnpbook" />
|
||||
</td>
|
||||
<td className={styles.itemType}>
|
||||
<ItemType.Top type="xnpdata" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={styles.itemType}>
|
||||
<ItemType.Top type="xnpmodel" />
|
||||
</td>
|
||||
<td className={styles.itemType}>
|
||||
<ItemType.Top type="xnpurl" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default DatabaseTop;
|
||||
@@ -1,42 +0,0 @@
|
||||
import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
|
||||
import AdvancedSearchBase from '../lib/AdvancedSearchBase';
|
||||
|
||||
interface Props {
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class BookAdvancedSearch extends AdvancedSearchBase {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.type = 'book';
|
||||
this.title = 'Book';
|
||||
this.state.values['title'] = '';
|
||||
this.state.values['keyword'] = '';
|
||||
this.state.values['description'] = '';
|
||||
this.state.values['doi'] = '';
|
||||
this.state.values['author'] = '';
|
||||
this.state.values['editor'] = '';
|
||||
this.state.values['publisher'] = '';
|
||||
this.state.values['publication_year'] = '';
|
||||
this.state.values['isbn'] = '';
|
||||
this.state.values['file.book_pdf.original_file_name'] = '';
|
||||
}
|
||||
|
||||
getRows() {
|
||||
const rows = [
|
||||
{ title: 'Title', value: this.renderFieldInputText('title', 50) },
|
||||
{ title: 'Free Keywords', value: this.renderFieldInputText('keyword', 50) },
|
||||
{ title: 'Description', value: this.renderFieldInputText('description', 50) },
|
||||
{ title: 'ID', value: this.renderFieldInputText('doi', 50) },
|
||||
{ title: 'Author', value: this.renderFieldInputText('author', 50) },
|
||||
{ title: 'Editor', value: this.renderFieldInputText('editor', 50) },
|
||||
{ title: 'Publisher', value: this.renderFieldInputText('publisher', 50) },
|
||||
{ title: 'Publication Year', value: this.renderFieldInputText('publication_year', 10) },
|
||||
{ title: 'ISBN', value: this.renderFieldInputText('isbn', 50) },
|
||||
{ title: 'PDF File', value: this.renderFieldInputText('file.book_pdf.original_file_name', 50) },
|
||||
];
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default BookAdvancedSearch;
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemBook } from '../../lib/ItemUtil';
|
||||
import DetailBase, { DetailBaseProps } from '../lib/DetailBase';
|
||||
import ItemTypeField from '../lib/field';
|
||||
|
||||
class BookDetail extends DetailBase {
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
const item = this.item as ItemBook;
|
||||
this.fields = [
|
||||
{ label: 'ID', value: item.doi },
|
||||
{ label: 'Language', value: <ItemTypeField.Language lang={item.lang} /> },
|
||||
{ label: 'Title', value: item.title },
|
||||
{ label: 'Free Keywords', value: <ItemTypeField.FreeKeyword keyword={item.keyword} /> },
|
||||
{ label: 'Description', value: <ItemTypeField.Description description={item.description} /> },
|
||||
{ label: 'Last Modified Date', value: <ItemTypeField.DateTime date={item.last_update_date} /> },
|
||||
{ label: 'Created Date', value: <ItemTypeField.DateTime date={item.creation_date} /> },
|
||||
{ label: 'Contributor', value: <ItemTypeField.Contributer uname={item.uname} name={item.name} /> },
|
||||
{ label: 'Item Type', value: item.item_type_display_name },
|
||||
{ label: 'Change Log(History)', value: <ItemTypeField.ChangeLog changelog={item.changelog} /> },
|
||||
{ label: 'Author', value: <ItemTypeField.Author author={item.author} /> },
|
||||
{ label: 'Editor', value: item.editor },
|
||||
{ label: 'Publisher', value: item.publisher },
|
||||
{ label: 'Publication Year', value: item.publication_year },
|
||||
{ label: 'URL', value: <a href={item.url} target="_blank" rel="noopener noreferrer">{item.url}</a> },
|
||||
{ label: 'PDF File', value: <ItemTypeField.ItemFile file={item.file} type="book_pdf" /> },
|
||||
{ label: 'Index', value: <ItemTypeField.ItemIndex index={item.index} /> },
|
||||
{ label: 'Related to', value: <ItemTypeField.RelatedTo relatedTo={item.related_to} /> },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default BookDetail;
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemConference } from '../../lib/ItemUtil';
|
||||
import DetailBase, { DetailBaseProps } from '../lib/DetailBase';
|
||||
import ItemTypeField from '../lib/field';
|
||||
import ConferenceUtil from './ConferenceUtil';
|
||||
|
||||
class ConferenceDetail extends DetailBase {
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
const item = this.item as ItemConference;
|
||||
this.fields = [
|
||||
{ label: 'ID', value: item.doi },
|
||||
{ label: 'Language', value: <ItemTypeField.Language lang={item.lang} /> },
|
||||
{ label: 'Conference Title', value: item.conference_title },
|
||||
{ label: 'Place', value: item.place },
|
||||
{ label: 'Date', value: <ConferenceUtil.ConferenceDate item={item} /> },
|
||||
{ label: 'Last Modified Date', value: <ItemTypeField.DateTime date={item.last_update_date} /> },
|
||||
{ label: 'Created Date', value: <ItemTypeField.DateTime date={item.creation_date} /> },
|
||||
{ label: 'Contributor', value: <ItemTypeField.Contributer uname={item.uname} name={item.name} /> },
|
||||
{ label: 'Item Type', value: item.item_type_display_name },
|
||||
{ label: 'Change Log(History)', value: <ItemTypeField.ChangeLog changelog={item.changelog} /> },
|
||||
{ label: 'Presentation Title', value: item.title },
|
||||
{ label: 'Author', value: <ItemTypeField.Author author={item.author} /> },
|
||||
{ label: 'Abstract', value: <ItemTypeField.Description description={item.abstract} /> },
|
||||
{ label: 'Presentation File', value: <ItemTypeField.ItemFile file={item.file} type="conference_file" /> },
|
||||
{ label: 'Presentation Type', value: <ConferenceUtil.PresentationType type={item.presentation_type} /> },
|
||||
{ label: 'Conference Paper', value: <ItemTypeField.ItemFile file={item.file} type="conference_paper" /> },
|
||||
{ label: 'Index', value: <ItemTypeField.ItemIndex index={item.index} /> },
|
||||
{ label: 'Related to', value: <ItemTypeField.RelatedTo relatedTo={item.related_to} /> },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default ConferenceDetail;
|
||||
@@ -1,50 +0,0 @@
|
||||
import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
|
||||
import AdvancedSearchBase from '../lib/AdvancedSearchBase';
|
||||
import { ItemDataSubTypes } from '../../lib/ItemUtil';
|
||||
|
||||
interface Props {
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class DataAdvancedSearch extends AdvancedSearchBase {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.type = 'data';
|
||||
this.title = 'Data';
|
||||
const now = new Date();
|
||||
const year = String(now.getFullYear());
|
||||
const month = String(now.getMonth() + 1);
|
||||
const mday = String(now.getDate());
|
||||
this.state.values['title'] = '';
|
||||
this.state.values['keyword'] = '';
|
||||
this.state.values['description'] = '';
|
||||
this.state.values['doi'] = '';
|
||||
this.state.values['data_type'] = '';
|
||||
this.state.values['experimenter'] = '';
|
||||
this.state.values['publication_year'] = year;
|
||||
this.state.values['publication_month'] = month;
|
||||
this.state.values['publication_mday'] = mday;
|
||||
this.state.values['file.preview.caption'] = '';
|
||||
this.state.values['file.data_file.original_file_name'] = '';
|
||||
this.setIgnoreKey('publication_year');
|
||||
this.setIgnoreKey('publication_month');
|
||||
this.setIgnoreKey('publication_mday');
|
||||
}
|
||||
|
||||
getRows() {
|
||||
const rows = [
|
||||
{ title: 'Title', value: this.renderFieldInputText('title', 50) },
|
||||
{ title: 'Free Keywords', value: this.renderFieldInputText('keyword', 50) },
|
||||
{ title: 'Description', value: this.renderFieldInputText('description', 50) },
|
||||
{ title: 'ID', value: this.renderFieldInputText('doi', 50) },
|
||||
{ title: 'Data Type', value: this.renderFieldSelect('data_type', ItemDataSubTypes) },
|
||||
{ title: 'Experimenter', value: this.renderFieldInputText('experimenter', 50) },
|
||||
{ title: 'Date', value: this.renderFieldDate('', 'publication_year', 'publication_month', 'publication_mday') },
|
||||
{ title: 'Caption', value: this.renderFieldInputText('file.preview.caption', 50) },
|
||||
{ title: 'Data File', value: this.renderFieldInputText('file.data_file.original_file_name', 50) },
|
||||
];
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default DataAdvancedSearch;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemData } from '../../lib/ItemUtil';
|
||||
import DetailBase, { DetailBaseProps } from '../lib/DetailBase';
|
||||
import ItemTypeField from '../lib/field';
|
||||
import DataUtil from './DataUtil';
|
||||
|
||||
class DataDetail extends DetailBase {
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
const item = this.item as ItemData;
|
||||
this.fields = [
|
||||
{ label: 'ID', value: item.doi },
|
||||
{ label: 'Language', value: <ItemTypeField.Language lang={item.lang} /> },
|
||||
{ label: 'Title', value: item.title },
|
||||
{ label: 'Free Keywords', value: <ItemTypeField.FreeKeyword keyword={item.keyword} /> },
|
||||
{ label: 'Description', value: <ItemTypeField.Description description={item.description} /> },
|
||||
{ label: 'Date', value: <ItemTypeField.PublicationDate year={item.publication_year} month={item.publication_month} mday={item.publication_mday} /> },
|
||||
{ label: 'Last Modified Date', value: <ItemTypeField.DateTime date={item.last_update_date} /> },
|
||||
{ label: 'Created Date', value: <ItemTypeField.DateTime date={item.creation_date} /> },
|
||||
{ label: 'Contributor', value: <ItemTypeField.Contributer uname={item.uname} name={item.name} /> },
|
||||
{ label: 'Item Type', value: item.item_type_display_name },
|
||||
{ label: 'Change Log(History)', value: <ItemTypeField.ChangeLog changelog={item.changelog} /> },
|
||||
{ label: 'Data Type', value: <DataUtil.DataType type={item.data_type} /> },
|
||||
{ label: 'Experimenter', value: <ItemTypeField.Author author={item.experimenter} /> },
|
||||
{ label: 'Preview', value: <ItemTypeField.Preview file={item.file} /> },
|
||||
{ label: 'Data File', value: <ItemTypeField.ItemFile file={item.file} type="data_file" rights={item.rights} useCc={item.use_cc} ccCommercialUse={item.cc_commercial_use} ccModification={item.cc_modification} /> },
|
||||
{ label: 'Readme', value: <ItemTypeField.Readme readme={item.readme} /> },
|
||||
{ label: 'Rights', value: <ItemTypeField.Rights rights={item.rights} useCc={item.use_cc} ccCommercialUse={item.cc_commercial_use} ccModification={item.cc_modification} /> },
|
||||
{ label: 'Index', value: <ItemTypeField.ItemIndex index={item.index} /> },
|
||||
{ label: 'Related to', value: <ItemTypeField.RelatedTo relatedTo={item.related_to} /> },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default DataDetail;
|
||||
@@ -1,98 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Item, ItemBook, ItemConference, ItemData, ItemModel, ItemPaper, ItemUrl } from '../lib/ItemUtil';
|
||||
import AdvancedSearchQuery from '../lib/AdvancedSearchQuery';
|
||||
import ItemTypeBook from './book';
|
||||
import ItemTypeConference from './conference';
|
||||
import ItemTypeData from './data';
|
||||
import ItemTypePaper from './paper';
|
||||
import ItemTypeModel from './model';
|
||||
import ItemTypeUrl from './url';
|
||||
|
||||
const Top = (props: { type: string }) => {
|
||||
const { type } = props;
|
||||
switch (type) {
|
||||
case 'xnpbook':
|
||||
return <ItemTypeBook.Top />;
|
||||
case 'xnpconference':
|
||||
return <ItemTypeConference.Top />;
|
||||
case 'xnpdata':
|
||||
return <ItemTypeData.Top />;
|
||||
case 'xnpmodel':
|
||||
return <ItemTypeModel.Top />;
|
||||
case 'xnppaper':
|
||||
return <ItemTypePaper.Top />;
|
||||
case 'xnpurl':
|
||||
return <ItemTypeUrl.Top />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const List = (props: { item: Item }) => {
|
||||
const item = props.item;
|
||||
switch (item.item_type_name) {
|
||||
case 'xnpbook':
|
||||
return <ItemTypeBook.List item={item as ItemBook} />;
|
||||
case 'xnpconference':
|
||||
return <ItemTypeConference.List item={item as ItemConference} />;
|
||||
case 'xnpdata':
|
||||
return <ItemTypeData.List item={item as ItemData} />;
|
||||
case 'xnpmodel':
|
||||
return <ItemTypeModel.List item={item as ItemModel} />;
|
||||
case 'xnppaper':
|
||||
return <ItemTypePaper.List item={item as ItemPaper} />;
|
||||
case 'xnpurl':
|
||||
return <ItemTypeUrl.List item={item as ItemUrl} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const Detail = (props: { item: Item }) => {
|
||||
const item = props.item;
|
||||
switch (item.item_type_name) {
|
||||
case 'xnpbook':
|
||||
return <ItemTypeBook.Detail item={item as ItemBook} />;
|
||||
case 'xnpconference':
|
||||
return <ItemTypeConference.Detail item={item as ItemConference} />;
|
||||
case 'xnpdata':
|
||||
return <ItemTypeData.Detail item={item as ItemData} />;
|
||||
case 'xnpmodel':
|
||||
return <ItemTypeModel.Detail item={item as ItemModel} />;
|
||||
case 'xnppaper':
|
||||
return <ItemTypePaper.Detail item={item as ItemPaper} />;
|
||||
case 'xnpurl':
|
||||
return <ItemTypeUrl.Detail item={item as ItemUrl} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const AdvancedSearch = (props: { type: string, query: AdvancedSearchQuery }) => {
|
||||
const { type, query } = props;
|
||||
switch (type) {
|
||||
case 'xnpbook':
|
||||
return <ItemTypeBook.AdvancedSearch query={query} />;
|
||||
case 'xnpconference':
|
||||
return <ItemTypeConference.AdvancedSearch query={query} />;
|
||||
case 'xnpdata':
|
||||
return <ItemTypeData.AdvancedSearch query={query} />;
|
||||
case 'xnpmodel':
|
||||
return <ItemTypeModel.AdvancedSearch query={query} />;
|
||||
case 'xnppaper':
|
||||
return <ItemTypePaper.AdvancedSearch query={query} />;
|
||||
case 'xnpurl':
|
||||
return <ItemTypeUrl.AdvancedSearch query={query} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const ItemType = {
|
||||
Top,
|
||||
List,
|
||||
Detail,
|
||||
AdvancedSearch
|
||||
}
|
||||
|
||||
export default ItemType;
|
||||
@@ -1,40 +0,0 @@
|
||||
import React, { ReactNode, Component } from 'react';
|
||||
import ItemUtil, { Item } from '../../lib/ItemUtil';
|
||||
|
||||
export interface DetailBaseProps { item: Item }
|
||||
export interface DetailBaseField { label: ReactNode, value: ReactNode }
|
||||
|
||||
class DetailBase extends Component<DetailBaseProps> {
|
||||
|
||||
protected item: Item;
|
||||
protected fields: DetailBaseField[] = [];
|
||||
protected url: string;
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
this.item = props.item;
|
||||
this.url = ItemUtil.getUrl(this.item);
|
||||
}
|
||||
|
||||
render() {
|
||||
let evenodd = 'even';
|
||||
const elements = this.fields.map((value, i) => {
|
||||
evenodd = evenodd === 'even' ? 'odd' : 'even'
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td className="head">{value.label}</td>
|
||||
<td className={evenodd}>{value.value}</td>
|
||||
</tr>
|
||||
)
|
||||
});
|
||||
return (
|
||||
<table className="outer itemDetail">
|
||||
<tbody>
|
||||
{elements}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DetailBase;
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Author = (props: { author: string[] }) => {
|
||||
const { author } = props;
|
||||
if (author.length === 0) {
|
||||
return null;
|
||||
}
|
||||
let evenodd = 'even';
|
||||
const elements = author.map((value, i) => {
|
||||
evenodd = evenodd === 'even' ? 'odd' : 'even';
|
||||
return (<tr key={i}><td className={evenodd}>{value}</td></tr>);
|
||||
});
|
||||
return (<table><tbody>{elements}</tbody></table>);
|
||||
}
|
||||
|
||||
export default Author;
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemBasicChangeLog } from '../../../lib/ItemUtil';
|
||||
import DateTime from './DateTime';
|
||||
|
||||
const ChangeLog = (props: { changelog: ItemBasicChangeLog[] }) => {
|
||||
const { changelog } = props;
|
||||
if (changelog.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const elements = changelog.map((value, i) => {
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td><DateTime date={value.log_date} onlyDate={true} /></td>
|
||||
<td>{value.log}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (<table><tbody>{elements}</tbody></table>);
|
||||
}
|
||||
|
||||
export default ChangeLog;
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Contributer = (props: { uname: string, name: string }) => {
|
||||
const { name, uname } = props;
|
||||
const label = (name === '' ? uname : name + ' (' + uname + ')');
|
||||
return (<span>{label}</span>);
|
||||
}
|
||||
|
||||
export default Contributer;
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const DateTime = (props: { date: number, onlyDate?: boolean }) => {
|
||||
const { date, onlyDate } = props;
|
||||
const d = new Date(date * 1000);
|
||||
const monthStr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const year = d.getFullYear();
|
||||
const month = monthStr[d.getMonth()];
|
||||
const mday = String(d.getDate()).padStart(2, '0');
|
||||
const hour = String(d.getHours()).padStart(2, '0');
|
||||
const min = String(d.getMinutes()).padStart(2, '0');
|
||||
const sec = String(d.getSeconds()).padStart(2, '0');
|
||||
const time = (typeof onlyDate !== 'undefined' || onlyDate) ? '' : ' ' + hour + ':' + min + ':' + sec;
|
||||
const label = month + ' ' + mday + ', ' + year + time;
|
||||
return (<span>{label}</span>);
|
||||
}
|
||||
|
||||
export default DateTime;
|
||||
@@ -1,16 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
const Description = (props: { description: string, className?: string }) => {
|
||||
const { description, className } = props;
|
||||
const regex = /(\r?\n)/g;
|
||||
const textarea = description.split(regex).map((line, i) => {
|
||||
if (line.match(regex)) {
|
||||
return <br key={i} />;
|
||||
}
|
||||
return <Fragment key={i}>{line}</Fragment>;
|
||||
});
|
||||
const name = typeof className === 'undefined' ? 'description' : className;
|
||||
return (<div className={name}>{textarea}</div>);
|
||||
}
|
||||
|
||||
export default Description;
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemBasicFile } from '../../../lib/ItemUtil';
|
||||
import DateTime from './DateTime';
|
||||
import FileDownloadButton from './FileDownloadButton'
|
||||
import FileSize from './FileSize';
|
||||
|
||||
const ItemFile = (props: { file: ItemBasicFile[], type: string, rights?: string, useCc?: number, ccCommercialUse?: number, ccModification?: number }) => {
|
||||
const { file, type } = props;
|
||||
const rights = typeof props.rights === 'undefined' ? '' : props.rights;
|
||||
const useCc = typeof props.useCc === 'undefined' ? 0 : props.useCc;
|
||||
const ccCommercialUse = typeof props.ccCommercialUse === 'undefined' ? 0 : props.ccCommercialUse;
|
||||
const ccModification = typeof props.ccModification === 'undefined' ? 0 : props.ccModification;
|
||||
const data = file.find((value) => {
|
||||
return value.file_type_name === type;
|
||||
});
|
||||
if (typeof data === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const date = new Date(data.timestamp);
|
||||
const timestamp = Math.floor(date.valueOf() / 1000);
|
||||
return (
|
||||
<div>
|
||||
{data.original_file_name}<br />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td>Type</td><td>: {data.mime_type}</td><td rowSpan={4}><FileDownloadButton file={data} rights={rights} useCc={useCc} ccCommercialUse={ccCommercialUse} ccModification={ccModification} /></td></tr>
|
||||
<tr><td>Size</td><td>: <FileSize size={data.file_size} /></td></tr>
|
||||
<tr><td>Last updated</td><td>: <DateTime date={timestamp} onlyDate={true} /></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ItemFile;
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemBasicLang } from '../../../lib/ItemUtil';
|
||||
|
||||
const Language = (props: { lang: ItemBasicLang }) => {
|
||||
const { lang } = props;
|
||||
const langStr = {
|
||||
eng: 'English',
|
||||
jpn: 'Japanese',
|
||||
fra: 'French',
|
||||
deu: 'German',
|
||||
esl: 'Spanish',
|
||||
ita: 'Italian',
|
||||
dut: 'Dutch',
|
||||
sve: 'Swedish',
|
||||
nor: 'Norwegian',
|
||||
dan: 'Danish',
|
||||
fin: 'Finnish',
|
||||
por: 'Portuguese',
|
||||
chi: 'Chinese',
|
||||
kor: 'Korean',
|
||||
}
|
||||
if (!(lang in langStr)) {
|
||||
return null;
|
||||
}
|
||||
const label = langStr[lang];
|
||||
return (<span>{label}</span>);
|
||||
}
|
||||
|
||||
export default Language;
|
||||
@@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
import ItemUtil, { ItemBasicFile } from '../../../lib/ItemUtil';
|
||||
import styles from './Preview.module.css';
|
||||
|
||||
const Preview = (props: { file: ItemBasicFile[] }) => {
|
||||
const { file } = props;
|
||||
const data = file.filter((value) => {
|
||||
return value.file_type_name === 'preview';
|
||||
});
|
||||
if (data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const previews = data.map((value) => {
|
||||
const fileUrl = ItemUtil.getPreviewFileUrl(value);
|
||||
return (
|
||||
<div key={value.file_id} className={styles.preview}>
|
||||
<a href={fileUrl} download={value.original_file_name}>
|
||||
<img src={fileUrl} alt={value.original_file_name} />
|
||||
</a>
|
||||
<caption>{value.caption}</caption>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className={styles.previewBox}>
|
||||
{previews}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Preview;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import DateTime from './DateTime';
|
||||
|
||||
const PublicationDate = (props: { year: number, month: number, mday: number }) => {
|
||||
const { year, month, mday } = props;
|
||||
const d = new Date(year + '-' + month + '-' + mday);
|
||||
const timestamp = Math.floor(d.valueOf() / 1000);
|
||||
return <DateTime date={timestamp} onlyDate={true} />;
|
||||
}
|
||||
|
||||
export default PublicationDate;
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
import Description from './Description';
|
||||
|
||||
const Readme = (props: { readme: string }) => {
|
||||
const { readme } = props;
|
||||
return <Description description={readme} className="readme" />;
|
||||
}
|
||||
|
||||
export default Readme;
|
||||
@@ -1,62 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import ItemUtil from '../../../lib/ItemUtil';
|
||||
import ItemType from '../..';
|
||||
|
||||
interface Props {
|
||||
relatedTo: number[];
|
||||
}
|
||||
|
||||
interface State {
|
||||
relatedTo: number[],
|
||||
elements: JSX.Element[];
|
||||
}
|
||||
|
||||
class RelatedTo extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
relatedTo: props.relatedTo,
|
||||
elements: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { relatedTo } = nextProps;
|
||||
this.setState({ relatedTo });
|
||||
this.updateElements(relatedTo);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateElements(this.state.relatedTo);
|
||||
}
|
||||
|
||||
async updateElements(relatedTo: number[]) {
|
||||
let evenodd = 'even';
|
||||
let elements: JSX.Element[] = [];
|
||||
for (let itemId of relatedTo) {
|
||||
const item = await ItemUtil.get(itemId);
|
||||
if (item !== null) {
|
||||
evenodd = evenodd === 'even' ? 'odd' : 'even';
|
||||
elements.push(<tr key={itemId}><td className={evenodd}><ItemType.List item={item} /></td></tr>);
|
||||
}
|
||||
}
|
||||
this.setState({ elements });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.relatedTo.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Item summary</th></tr>
|
||||
{this.state.elements}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RelatedTo;
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import Description from './Description';
|
||||
import CreativeCommons, { getCreativeCommonsType } from './CreativeCommons';
|
||||
|
||||
const Rights = (props: { rights: string, useCc: number, ccCommercialUse: number, ccModification: number }) => {
|
||||
const { rights, useCc, ccCommercialUse, ccModification } = props;
|
||||
if (useCc === 0) {
|
||||
return <Description description={rights} className="rights" />;
|
||||
}
|
||||
const ccType = getCreativeCommonsType(ccCommercialUse, ccModification);
|
||||
return <CreativeCommons type={ccType} />;
|
||||
}
|
||||
|
||||
export default Rights;
|
||||
@@ -1,39 +0,0 @@
|
||||
import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
|
||||
import AdvancedSearchBase from '../lib/AdvancedSearchBase';
|
||||
import { ItemModelSubTypes } from '../../lib/ItemUtil';
|
||||
|
||||
interface Props {
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class ModelAdvancedSearch extends AdvancedSearchBase {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.type = 'model';
|
||||
this.title = 'Model';
|
||||
this.state.values['title'] = '';
|
||||
this.state.values['keyword'] = '';
|
||||
this.state.values['description'] = '';
|
||||
this.state.values['doi'] = '';
|
||||
this.state.values['model_type'] = '';
|
||||
this.state.values['creator'] = '';
|
||||
this.state.values['file.preview.caption'] = '';
|
||||
this.state.values['file.model_data.original_file_name'] = '';
|
||||
}
|
||||
|
||||
getRows() {
|
||||
const rows = [
|
||||
{ title: 'Title', value: this.renderFieldInputText('title', 50) },
|
||||
{ title: 'Free Keywords', value: this.renderFieldInputText('keyword', 50) },
|
||||
{ title: 'Description', value: this.renderFieldInputText('description', 50) },
|
||||
{ title: 'ID', value: this.renderFieldInputText('doi', 50) },
|
||||
{ title: 'Model Type', value: this.renderFieldSelect('model_type', ItemModelSubTypes) },
|
||||
{ title: 'Creator', value: this.renderFieldInputText('creator', 50) },
|
||||
{ title: 'Caption', value: this.renderFieldInputText('file.preview.caption', 50) },
|
||||
{ title: 'Model File', value: this.renderFieldInputText('file.model_data.original_file_name', 50) },
|
||||
];
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default ModelAdvancedSearch;
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemModel } from '../../lib/ItemUtil';
|
||||
import DetailBase, { DetailBaseProps } from '../lib/DetailBase';
|
||||
import ItemTypeField from '../lib/field';
|
||||
import ModelUtil from './ModelUtil';
|
||||
|
||||
class ModelDetail extends DetailBase {
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
const item = this.item as ItemModel;
|
||||
this.fields = [
|
||||
{ label: 'ID', value: item.doi },
|
||||
{ label: 'Language', value: <ItemTypeField.Language lang={item.lang} /> },
|
||||
{ label: 'Title', value: item.title },
|
||||
{ label: 'Free Keywords', value: <ItemTypeField.FreeKeyword keyword={item.keyword} /> },
|
||||
{ label: 'Description', value: <ItemTypeField.Description description={item.description} /> },
|
||||
{ label: 'Last Modified Date', value: <ItemTypeField.DateTime date={item.last_update_date} /> },
|
||||
{ label: 'Created Date', value: <ItemTypeField.DateTime date={item.creation_date} /> },
|
||||
{ label: 'Contributor', value: <ItemTypeField.Contributer uname={item.uname} name={item.name} /> },
|
||||
{ label: 'Item Type', value: item.item_type_display_name },
|
||||
{ label: 'Change Log(History)', value: <ItemTypeField.ChangeLog changelog={item.changelog} /> },
|
||||
{ label: 'Model Type', value: <ModelUtil.ModelType type={item.model_type} /> },
|
||||
{ label: 'Creator', value: <ItemTypeField.Author author={item.creator} /> },
|
||||
{ label: 'Preview', value: <ItemTypeField.Preview file={item.file} /> },
|
||||
{ label: 'Model File', value: <ItemTypeField.ItemFile file={item.file} type="model_data" rights={item.rights} useCc={item.use_cc} ccCommercialUse={item.cc_commercial_use} ccModification={item.cc_modification} /> },
|
||||
{ label: 'Readme', value: <ItemTypeField.Readme readme={item.readme} /> },
|
||||
{ label: 'Rights', value: <ItemTypeField.Rights rights={item.rights} useCc={item.use_cc} ccCommercialUse={item.cc_commercial_use} ccModification={item.cc_modification} /> },
|
||||
{ label: 'Index', value: <ItemTypeField.ItemIndex index={item.index} /> },
|
||||
{ label: 'Related to', value: <ItemTypeField.RelatedTo relatedTo={item.related_to} /> },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default ModelDetail;
|
||||
@@ -1,44 +0,0 @@
|
||||
import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
|
||||
import AdvancedSearchBase from '../lib/AdvancedSearchBase';
|
||||
|
||||
interface Props {
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class PaperAdvancedSearch extends AdvancedSearchBase {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.type = 'paper';
|
||||
this.title = 'Paper';
|
||||
this.state.values['pubmed_id'] = '';
|
||||
this.state.values['title'] = '';
|
||||
this.state.values['keyword'] = '';
|
||||
this.state.values['description'] = '';
|
||||
this.state.values['doi'] = '';
|
||||
this.state.values['author'] = '';
|
||||
this.state.values['journal'] = '';
|
||||
this.state.values['publication_year'] = '';
|
||||
this.state.values['volume'] = '';
|
||||
this.state.values['number'] = '';
|
||||
this.state.values['page'] = '';
|
||||
}
|
||||
|
||||
getRows() {
|
||||
const rows = [
|
||||
{ title: 'PubMed ID', value: this.renderFieldInputText('pubmed_id', 50) },
|
||||
{ title: 'Title', value: this.renderFieldInputText('title', 50) },
|
||||
{ title: 'Free Keywords', value: this.renderFieldInputText('keyword', 50) },
|
||||
{ title: 'Description', value: this.renderFieldInputText('description', 50) },
|
||||
{ title: 'ID', value: this.renderFieldInputText('doi', 50) },
|
||||
{ title: 'Author', value: this.renderFieldInputText('author', 50) },
|
||||
{ title: 'Journal', value: this.renderFieldInputText('journal', 50) },
|
||||
{ title: 'Publication Year', value: this.renderFieldInputText('publication_year', 10) },
|
||||
{ title: 'Volume', value: this.renderFieldInputText('volume', 50) },
|
||||
{ title: 'Number', value: this.renderFieldInputText('number', 50) },
|
||||
{ title: 'Page', value: this.renderFieldInputText('page', 50) },
|
||||
];
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default PaperAdvancedSearch;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemPaper } from '../../lib/ItemUtil';
|
||||
import DetailBase, { DetailBaseProps } from '../lib/DetailBase';
|
||||
import ItemTypeField from '../lib/field';
|
||||
import PaperUtil from './PaperUtil';
|
||||
|
||||
class PaperDetail extends DetailBase {
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
const item = this.item as ItemPaper;
|
||||
this.fields = [
|
||||
{ label: 'ID', value: item.doi },
|
||||
{ label: 'Language', value: <ItemTypeField.Language lang={item.lang} /> },
|
||||
{ label: 'PubmedID', value: <PaperUtil.PubmedLink pubmedId={item.pubmed_id} /> },
|
||||
{ label: 'Title', value: item.title },
|
||||
{ label: 'Free Keywords', value: <ItemTypeField.FreeKeyword keyword={item.keyword} /> },
|
||||
{ label: 'Description', value: <ItemTypeField.Description description={item.description} /> },
|
||||
{ label: 'Last Modified Date', value: <ItemTypeField.DateTime date={item.last_update_date} /> },
|
||||
{ label: 'Created Date', value: <ItemTypeField.DateTime date={item.creation_date} /> },
|
||||
{ label: 'Contributor', value: <ItemTypeField.Contributer uname={item.uname} name={item.name} /> },
|
||||
{ label: 'Item Type', value: item.item_type_display_name },
|
||||
{ label: 'Change Log(History)', value: <ItemTypeField.ChangeLog changelog={item.changelog} /> },
|
||||
{ label: 'Author', value: <ItemTypeField.Author author={item.author} /> },
|
||||
{ label: 'Journal', value: item.journal },
|
||||
{ label: 'Publication Year', value: item.publication_year },
|
||||
{ label: 'Volume', value: item.volume },
|
||||
{ label: 'Number', value: item.number },
|
||||
{ label: 'Page', value: item.page },
|
||||
{ label: 'Index', value: <ItemTypeField.ItemIndex index={item.index} /> },
|
||||
{ label: 'Related to', value: <ItemTypeField.RelatedTo relatedTo={item.related_to} /> },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default PaperDetail;
|
||||
@@ -1,32 +0,0 @@
|
||||
import AdvancedSearchQuery from '../../lib/AdvancedSearchQuery';
|
||||
import AdvancedSearchBase from '../lib/AdvancedSearchBase';
|
||||
|
||||
interface Props {
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class UrlAdvancedSearch extends AdvancedSearchBase {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.type = 'url';
|
||||
this.title = 'Url';
|
||||
this.state.values['title'] = '';
|
||||
this.state.values['keyword'] = '';
|
||||
this.state.values['description'] = '';
|
||||
this.state.values['doi'] = '';
|
||||
this.state.values['url'] = '';
|
||||
}
|
||||
|
||||
getRows() {
|
||||
const rows = [
|
||||
{ title: 'Title', value: this.renderFieldInputText('title', 50) },
|
||||
{ title: 'Free Keywords', value: this.renderFieldInputText('keyword', 50) },
|
||||
{ title: 'Description', value: this.renderFieldInputText('description', 50) },
|
||||
{ title: 'ID', value: this.renderFieldInputText('doi', 50) },
|
||||
{ title: 'URL', value: this.renderFieldInputText('url', 50) },
|
||||
];
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default UrlAdvancedSearch;
|
||||
@@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ItemUrl } from '../../lib/ItemUtil';
|
||||
import DetailBase, { DetailBaseProps } from '../lib/DetailBase';
|
||||
import ItemTypeField from '../lib/field';
|
||||
import UrlUtil from './UrlUtil';
|
||||
|
||||
class UrlDetail extends DetailBase {
|
||||
|
||||
constructor(props: DetailBaseProps) {
|
||||
super(props);
|
||||
const item = this.item as ItemUrl;
|
||||
this.fields = [
|
||||
{ label: 'ID', value: item.doi },
|
||||
{ label: 'Language', value: <ItemTypeField.Language lang={item.lang} /> },
|
||||
{ label: 'Title', value: item.title },
|
||||
{ label: 'Free Keywords', value: <ItemTypeField.FreeKeyword keyword={item.keyword} /> },
|
||||
{ label: 'Description', value: <ItemTypeField.Description description={item.description} /> },
|
||||
{ label: 'Last Modified Date', value: <ItemTypeField.DateTime date={item.last_update_date} /> },
|
||||
{ label: 'Created Date', value: <ItemTypeField.DateTime date={item.creation_date} /> },
|
||||
{ label: 'Contributor', value: <ItemTypeField.Contributer uname={item.uname} name={item.name} /> },
|
||||
{ label: 'Item Type', value: item.item_type_display_name },
|
||||
{ label: 'Change Log(History)', value: <ItemTypeField.ChangeLog changelog={item.changelog} /> },
|
||||
{ label: 'URL', value: <a href={item.url} target="_blank" rel="noopener noreferrer">{item.url}</a> },
|
||||
{ label: 'Banner File', value: <UrlUtil.BannerFile file={item.file} /> },
|
||||
{ label: 'Index', value: <ItemTypeField.ItemIndex index={item.index} /> },
|
||||
{ label: 'Related to', value: <ItemTypeField.RelatedTo relatedTo={item.related_to} /> },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default UrlDetail;
|
||||
@@ -1,98 +0,0 @@
|
||||
import loki from 'lokijs';
|
||||
import indexesJson from '../../../assets/tree.json';
|
||||
|
||||
export interface Index {
|
||||
id: number;
|
||||
title: string;
|
||||
numOfItems: number;
|
||||
parentId: number;
|
||||
}
|
||||
|
||||
export const INDEX_ID_ROOT = 1;
|
||||
export const INDEX_ID_PUBLIC = 3;
|
||||
|
||||
interface IndexJson {
|
||||
id: number;
|
||||
title: string;
|
||||
num_of_items: number;
|
||||
children: IndexJson[];
|
||||
}
|
||||
|
||||
interface LokiColEntry {
|
||||
key: number;
|
||||
index: Index;
|
||||
}
|
||||
|
||||
const lokiDB = new loki('database');
|
||||
const lokiCol: Collection<any> = lokiDB.addCollection('indexes');
|
||||
let lokiColKey: number = 0;
|
||||
const store = (jsonArr: IndexJson[], parentId: number) => {
|
||||
jsonArr.forEach((json: IndexJson) => {
|
||||
const entry: LokiColEntry = {
|
||||
key: ++lokiColKey,
|
||||
index: {
|
||||
id: json.id,
|
||||
title: json.title,
|
||||
numOfItems: json.num_of_items,
|
||||
parentId: parentId,
|
||||
},
|
||||
};
|
||||
lokiCol.insert(entry);
|
||||
store(json.children, json.id)
|
||||
});
|
||||
}
|
||||
store(indexesJson, INDEX_ID_ROOT);
|
||||
|
||||
class IndexUtil {
|
||||
|
||||
static getUrl(id: number): string {
|
||||
return '/database/list/' + String(id);
|
||||
}
|
||||
|
||||
static get(indexId: number): Index | null {
|
||||
const filter = {
|
||||
'index.id': indexId,
|
||||
}
|
||||
const res = lokiCol.findOne(filter);
|
||||
if (res === null) {
|
||||
return null;
|
||||
}
|
||||
return res.index as Index;
|
||||
}
|
||||
|
||||
static getChildren(indexId: number): Index[] {
|
||||
const filter = {
|
||||
'index.parentId': indexId,
|
||||
}
|
||||
const res = lokiCol.chain().find(filter).simplesort('key').data();
|
||||
return res.map((entry: LokiColEntry) => {
|
||||
return entry.index;
|
||||
});
|
||||
}
|
||||
|
||||
static countChildren(indexId: number): number {
|
||||
const filter = {
|
||||
'index.parentId': indexId,
|
||||
}
|
||||
const res = lokiCol.count(filter);
|
||||
return res;
|
||||
}
|
||||
|
||||
static getParents(parentId: number): Index[] {
|
||||
let parents: Index[] = [];
|
||||
const loop = (parentId: number) => {
|
||||
if (parentId !== INDEX_ID_ROOT) {
|
||||
const parent = this.get(parentId);
|
||||
if (parent !== null) {
|
||||
loop(parent.parentId);
|
||||
parents.push(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
loop(parentId);
|
||||
return parents;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default IndexUtil;
|
||||
@@ -1,516 +0,0 @@
|
||||
import loki from 'lokijs';
|
||||
import axios from 'axios';
|
||||
import funcs from '../../lib/Functions';
|
||||
import AdvancedSearchQuery from './AdvancedSearchQuery';
|
||||
const LokiIndexedAdapter = require('lokijs/src/loki-indexed-adapter.js');
|
||||
|
||||
export const APPLICATION_NAME = 'pupil';
|
||||
export const APPLICATION_VERSION = 0;
|
||||
export const APPLICATION_USE_INDEXEDDB = false;
|
||||
|
||||
export type ItemBasicLang = 'eng' | 'jpn' | 'fra' | 'deu' | 'esl' | 'ita' | 'dut' | 'sve' | 'nor' | 'dan' | 'fin' | 'por' | 'chi' | 'kor';
|
||||
export interface ItemBasicIndex {
|
||||
index_id: number;
|
||||
title: string;
|
||||
}
|
||||
export interface ItemBasicChangeLog {
|
||||
log_date: number;
|
||||
log: string;
|
||||
}
|
||||
export interface ItemBasicFile {
|
||||
file_id: number;
|
||||
original_file_name: string;
|
||||
mime_type: string;
|
||||
file_size: number;
|
||||
caption: string;
|
||||
timestamp: string;
|
||||
file_type_name: string;
|
||||
file_type_display_name: string;
|
||||
}
|
||||
export interface ItemBasic {
|
||||
item_id: number;
|
||||
item_type_id: number;
|
||||
uid: number;
|
||||
description: string;
|
||||
doi: string;
|
||||
last_update_date: number;
|
||||
creation_date: number;
|
||||
publication_year: number;
|
||||
publication_month: number;
|
||||
publication_mday: number;
|
||||
lang: ItemBasicLang;
|
||||
title: string;
|
||||
item_type_display_name: string;
|
||||
item_type_name: string;
|
||||
uname: string;
|
||||
name: string;
|
||||
item_url: string;
|
||||
index: ItemBasicIndex[];
|
||||
changelog: ItemBasicChangeLog[];
|
||||
related_to: number[];
|
||||
keyword: string[];
|
||||
file: ItemBasicFile[];
|
||||
}
|
||||
|
||||
export interface ItemBook extends ItemBasic {
|
||||
book_id: number;
|
||||
classfication: string;
|
||||
editor: string;
|
||||
publisher: string;
|
||||
isbn: string;
|
||||
url: string;
|
||||
attachment_dl_limit: number;
|
||||
attachment_dl_notify: number;
|
||||
author: string[];
|
||||
}
|
||||
|
||||
export type ItemConferenceSubType = 'powerpoint' | 'pdf' | 'illustrator' | 'other';
|
||||
export interface ItemConference extends ItemBasic {
|
||||
conference_id: number;
|
||||
presentation_type: ItemConferenceSubType;
|
||||
conference_title: string;
|
||||
place: string;
|
||||
abstract: string;
|
||||
conference_from_year: number;
|
||||
conference_from_month: number;
|
||||
conference_from_mday: number;
|
||||
conference_to_year: number;
|
||||
conference_to_month: number;
|
||||
conference_to_mday: number;
|
||||
attachment_dl_limit: number;
|
||||
attachment_dl_notify: number;
|
||||
author: string[];
|
||||
}
|
||||
|
||||
export type ItemDataSubType = 'excel' | 'movie' | 'text' | 'picture' | 'other';
|
||||
export interface ItemData extends ItemBasic {
|
||||
data_id: number;
|
||||
data_type: ItemDataSubType;
|
||||
rights: string;
|
||||
readme: string;
|
||||
use_cc: number;
|
||||
cc_commercial_use: number;
|
||||
cc_modification: number;
|
||||
attachment_dl_limit: number;
|
||||
attachment_dl_notify: number;
|
||||
experimenter: string[];
|
||||
}
|
||||
|
||||
export type ItemModelSubType = 'matlab' | 'neuron' | 'original_program' | 'satellite' | 'genesis' | 'a_cell' | 'other';
|
||||
export interface ItemModel extends ItemBasic {
|
||||
model_id: number;
|
||||
model_type: ItemModelSubType;
|
||||
readme: string;
|
||||
rights: string;
|
||||
use_cc: number;
|
||||
cc_commercial_use: number;
|
||||
cc_modification: number;
|
||||
attachment_dl_limit: number;
|
||||
attachment_dl_notify: number;
|
||||
creator: string[];
|
||||
}
|
||||
|
||||
export interface ItemPaper extends ItemBasic {
|
||||
paper_id: number;
|
||||
journal: string;
|
||||
volume: number;
|
||||
number: number | null;
|
||||
page: string;
|
||||
abstract: string;
|
||||
pubmed_id: string;
|
||||
author: string[];
|
||||
}
|
||||
|
||||
export interface ItemUrl extends ItemBasic {
|
||||
url_id: number;
|
||||
url: string;
|
||||
url_count: number;
|
||||
}
|
||||
|
||||
export type Item = ItemBook | ItemConference | ItemData | ItemModel | ItemPaper | ItemUrl;
|
||||
|
||||
export interface ItemSubType<T> {
|
||||
type: T;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type ItemSubTypes<T> = readonly ItemSubType<T>[];
|
||||
|
||||
export const ItemConferenceSubTypes: ItemSubTypes<ItemConferenceSubType> = [
|
||||
{ type: 'powerpoint', label: 'PowerPoint' },
|
||||
{ type: 'pdf', label: 'PDF' },
|
||||
{ type: 'illustrator', label: 'Illustrator' },
|
||||
{ type: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const ItemDataSubTypes: ItemSubTypes<ItemDataSubType> = [
|
||||
{ type: 'excel', label: 'Excel' },
|
||||
{ type: 'movie', label: 'Movie' },
|
||||
{ type: 'text', label: 'Text' },
|
||||
{ type: 'picture', label: 'Picture' },
|
||||
{ type: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const ItemModelSubTypes: ItemSubTypes<ItemModelSubType> = [
|
||||
{ type: 'matlab', label: 'Matlab' },
|
||||
{ type: 'neuron', label: 'Neuron' },
|
||||
{ type: 'original_program', label: 'Original Program' },
|
||||
{ type: 'satellite', label: 'Satellite' },
|
||||
{ type: 'genesis', label: 'Genesis' },
|
||||
{ type: 'a_cell', label: 'A-Cell' },
|
||||
{ type: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
const SEARCH_BY_KEYWORD_QUERY_TYPE_RANGE = ['all', 'basic', 'book', 'conference', 'data', 'model', 'paper', 'url'];
|
||||
export type SearchByKeywordType = 'all' | 'basic' | 'book' | 'conference' | 'data' | 'model' | 'paper' | 'url';
|
||||
|
||||
export type SortConditionLimit = 20 | 50 | 100;
|
||||
export type SortConditionOrderBy = 'title' | 'doi' | 'last_update_date' | 'creation_date' | 'publication_date';
|
||||
export enum SortConditionOrderDir { ASC, DESC }
|
||||
|
||||
export interface SortCondition {
|
||||
limit: SortConditionLimit;
|
||||
orderBy: SortConditionOrderBy;
|
||||
orderDir: SortConditionOrderDir;
|
||||
page: number;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
total: number;
|
||||
data: Item[];
|
||||
}
|
||||
|
||||
class ItemDatabase {
|
||||
|
||||
private db: loki;
|
||||
private items: Collection<any> | null = null;
|
||||
private loading: boolean = true;
|
||||
private name: string;
|
||||
private version: number;
|
||||
private useIndexedDB: boolean;
|
||||
|
||||
constructor(name: string, version: number, useIndexedDB: boolean) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.useIndexedDB = useIndexedDB;
|
||||
if (this.useIndexedDB) {
|
||||
const idbAdapter = new LokiIndexedAdapter(this.name);
|
||||
const paAdapter = new loki.LokiPartitioningAdapter(idbAdapter, { paging: true });
|
||||
this.db = new loki('database', { adapter: paAdapter });
|
||||
this.loadDatabaseFromIndexedDB();
|
||||
} else {
|
||||
this.db = new loki('database');
|
||||
this.loadDatabaseFromJson();
|
||||
}
|
||||
}
|
||||
|
||||
loadDatabaseFromJson() {
|
||||
axios.get('/database/items.json', { responseType: 'json' }).then((response) => {
|
||||
const itemsJson: Item[] = response.data;
|
||||
const items = this.db.addCollection('items');
|
||||
if (items !== null) {
|
||||
itemsJson.forEach((json) => {
|
||||
items.insert(json);
|
||||
});
|
||||
this.items = items;
|
||||
if (this.useIndexedDB) {
|
||||
this.db.saveDatabase();
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadDatabaseFromIndexedDB() {
|
||||
this.db.loadDatabase({}, (err: any) => {
|
||||
let forceLoad = true;
|
||||
if (this.useIndexedDB) {
|
||||
let colVersion = this.db.getCollection('version');
|
||||
if (colVersion === null) {
|
||||
colVersion = this.db.addCollection('version');
|
||||
colVersion.insert({ type: 'items', version: this.version });
|
||||
} else {
|
||||
const v = colVersion.findOne({ type: 'items' });
|
||||
if (v === null) {
|
||||
colVersion.insert({ type: 'items', version: this.version });
|
||||
} else if (v.version !== this.version) {
|
||||
v.version = this.version;
|
||||
colVersion.update(v);
|
||||
} else {
|
||||
forceLoad = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forceLoad) {
|
||||
this.db.removeCollection('items');
|
||||
}
|
||||
this.items = this.db.getCollection('items');
|
||||
if (this.items !== null) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.loadDatabaseFromJson();
|
||||
});
|
||||
}
|
||||
|
||||
async getItems() {
|
||||
while (this.loading) {
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
return this.items as Collection<any>;
|
||||
}
|
||||
}
|
||||
|
||||
const database = new ItemDatabase(APPLICATION_NAME, APPLICATION_VERSION, APPLICATION_USE_INDEXEDDB);
|
||||
|
||||
class ItemSorter {
|
||||
|
||||
public orderBy: SortConditionOrderBy;
|
||||
public orderDir: SortConditionOrderDir;
|
||||
|
||||
constructor(condition: SortCondition) {
|
||||
this.orderBy = condition.orderBy;
|
||||
this.orderDir = condition.orderDir;
|
||||
this.sort = this.sort.bind(this);
|
||||
}
|
||||
|
||||
sort(a: Item, b: Item) {
|
||||
let av: string = '';
|
||||
let bv: string = '';
|
||||
switch (this.orderBy) {
|
||||
case 'title':
|
||||
av = a.title.toLocaleUpperCase();
|
||||
bv = b.title.toLocaleUpperCase();
|
||||
break;
|
||||
case 'doi':
|
||||
av = a.doi.toLocaleUpperCase();
|
||||
bv = b.doi.toLocaleUpperCase();
|
||||
break;
|
||||
case 'last_update_date':
|
||||
av = String(a.last_update_date);
|
||||
bv = String(b.last_update_date);
|
||||
break;
|
||||
case 'creation_date':
|
||||
av = String(a.creation_date);
|
||||
bv = String(b.creation_date);
|
||||
break;
|
||||
case 'publication_date':
|
||||
av = String(a.publication_year * 10000 + a.publication_month * 100 + a.publication_mday);
|
||||
bv = String(b.publication_year * 10000 + b.publication_month * 100 + b.publication_mday);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (this.orderDir === SortConditionOrderDir.ASC) {
|
||||
if (av > bv) return -1;
|
||||
else if (av < bv) return 1;
|
||||
} else {
|
||||
if (av > bv) return 1;
|
||||
else if (av < bv) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class ItemUtil {
|
||||
|
||||
static getUrl(item: Item) {
|
||||
if (item.doi !== '') {
|
||||
return '/database/item/id/' + funcs.escape(item.doi);
|
||||
}
|
||||
return '/database/item/' + funcs.escape(String(item.item_id));
|
||||
}
|
||||
|
||||
static getFileUrl(file: ItemBasicFile) {
|
||||
return '/database/file/' + funcs.escape(String(file.file_id)) + '/' + funcs.escape(file.original_file_name);
|
||||
}
|
||||
|
||||
static getPreviewFileUrl(file: ItemBasicFile) {
|
||||
return '/database/file/' + funcs.escape(String(file.file_id)) + '.png';
|
||||
}
|
||||
|
||||
static getSearchByKeywordUrl(type: SearchByKeywordType, keyword: string) {
|
||||
const params = new URLSearchParams({ type, keyword });
|
||||
return '/database/search?' + params.toString();
|
||||
}
|
||||
|
||||
static getItemTypeSearchUrl(type: string) {
|
||||
return '/database/search/itemtype/' + funcs.escape(type);
|
||||
}
|
||||
|
||||
static getSearchByAdvancedKeywordsUrl(query: AdvancedSearchQuery) {
|
||||
const paramString = query.getQueryParams().toString();
|
||||
return '/database/search/advanced' + (paramString.length > 0 ? '?' + paramString : '');
|
||||
}
|
||||
|
||||
static getSearchKeywordByQuery(queryString: string) {
|
||||
const query = new URLSearchParams(queryString);
|
||||
const qtype = query.get('type');
|
||||
const type = qtype !== null && SEARCH_BY_KEYWORD_QUERY_TYPE_RANGE.includes(qtype) ? qtype as SearchByKeywordType : 'all';
|
||||
const qkeyword = query.get('keyword');
|
||||
const keyword = qkeyword === null ? '' : qkeyword;
|
||||
return ({ type, keyword });
|
||||
}
|
||||
|
||||
static getAdvancedSearchQueryByQuery(queryString: string) {
|
||||
const query: AdvancedSearchQuery = new AdvancedSearchQuery();
|
||||
query.setByQueryString(queryString);
|
||||
return query;
|
||||
}
|
||||
|
||||
static async get(itemId: number) {
|
||||
const items = await database.getItems();
|
||||
const filter = {
|
||||
'item_id': itemId
|
||||
}
|
||||
const item = items.findOne(filter);
|
||||
return item;
|
||||
}
|
||||
|
||||
static async getByDoi(doi: string) {
|
||||
const items = await database.getItems();
|
||||
const filter = {
|
||||
'doi': doi
|
||||
}
|
||||
const item = items.findOne(filter);
|
||||
return item;
|
||||
}
|
||||
|
||||
static async getListByIndexId(indexId: number, condition: SortCondition) {
|
||||
const items = await database.getItems();
|
||||
const filter = {
|
||||
'index.index_id': indexId
|
||||
};
|
||||
const offset = condition.limit * (condition.page - 1);
|
||||
const result = items.chain().find(filter);
|
||||
const itemSorter = new ItemSorter(condition);
|
||||
const ret: SearchResult = {
|
||||
total: result.count(),
|
||||
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
static async getListByItemType(itemType: string, subItemType: string, condition: SortCondition) {
|
||||
const items = await database.getItems();
|
||||
let filter: any = {
|
||||
item_type_name: 'xnp' + itemType,
|
||||
}
|
||||
if (subItemType !== '') {
|
||||
switch (itemType) {
|
||||
case 'conference':
|
||||
filter.presentation_type = subItemType;
|
||||
break;
|
||||
case 'data':
|
||||
filter.data_type = subItemType;
|
||||
break;
|
||||
case 'model':
|
||||
filter.model_type = subItemType;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const offset = condition.limit * (condition.page - 1);
|
||||
const result = items.chain().find(filter);
|
||||
const itemSorter = new ItemSorter(condition);
|
||||
const ret = {
|
||||
total: result.count(),
|
||||
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
static async getListByKeyword(type: SearchByKeywordType, keyword: string, condition: SortCondition) {
|
||||
const items = await database.getItems();
|
||||
const regex = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const num = keyword.match(/^[0-9]+$/) ? parseInt(keyword, 10) : null;
|
||||
let filter: any = { '$or': [] };
|
||||
const appendToFilter = (type: string, strKeys: string[], numKeys: string[]) => {
|
||||
const basicStrKeys: string[] = ['title', 'keyword', 'doi', 'description', 'uname', 'name', 'index.title'];
|
||||
const basicNumKeys: string[] = [];
|
||||
let filterItemType: any = {
|
||||
'item_type_name': 'xnp' + type,
|
||||
'$or': []
|
||||
};
|
||||
let sKeys: string[] = basicStrKeys.concat(strKeys);
|
||||
let nKeys: string[] = basicNumKeys.concat(numKeys);
|
||||
sKeys.forEach((key) => {
|
||||
let criteria: any = {};
|
||||
criteria[key] = { '$regex': regex };
|
||||
filterItemType['$or'].push(criteria);
|
||||
});
|
||||
if (num !== null) {
|
||||
nKeys.forEach((key) => {
|
||||
let criteria: any = {};
|
||||
criteria[key] = { '$eq': num };
|
||||
filterItemType['$or'].push(criteria);
|
||||
});
|
||||
}
|
||||
filter['$or'].push(filterItemType);
|
||||
}
|
||||
if (type === 'basic') {
|
||||
filter['$or'].push({ 'title': { '$regex': regex } });
|
||||
filter['$or'].push({ 'keyword': { '$regex': regex } });
|
||||
} else {
|
||||
if (type === 'all' || type === 'book') {
|
||||
const bookStrKeys: string[] = ['title', 'keyword', 'classification', 'editor', 'publisher', 'isbn', 'url', 'author', 'file.caption', 'file.original_file_name'];
|
||||
const bookNumKeys: string[] = [];
|
||||
appendToFilter('book', bookStrKeys, bookNumKeys);
|
||||
}
|
||||
if (type === 'all' || type === 'conference') {
|
||||
const conferenceStrKeys: string[] = ['title', 'conference_title', 'place', 'abstract', 'author', 'file.caption', 'file.original_file_name'];
|
||||
const conferenceNumKeys: string[] = ['conference_from_year', 'conference_from_month', 'conference_from_mday', 'conference_to_year', 'conference_to_month', 'conference_to_mday'];
|
||||
appendToFilter('conference', conferenceStrKeys, conferenceNumKeys);
|
||||
}
|
||||
if (type === 'all' || type === 'data') {
|
||||
const dataStrKeys: string[] = ['title', 'keyword', 'rights', 'readme', 'experimenter', 'file.caption', 'file.original_file_name'];
|
||||
const dataNumKeys: string[] = [];
|
||||
appendToFilter('data', dataStrKeys, dataNumKeys);
|
||||
}
|
||||
if (type === 'all' || type === 'model') {
|
||||
const modelStrKeys: string[] = ['title', 'keyword', 'readme', 'rights', 'creator', 'file.caption', 'file.original_file_name'];
|
||||
const modelNumKeys: string[] = [];
|
||||
appendToFilter('model', modelStrKeys, modelNumKeys);
|
||||
}
|
||||
if (type === 'all' || type === 'paper') {
|
||||
const paperStrKeys: string[] = ['title', 'keyword', 'journal', 'page', 'pubmed_id', 'author'];
|
||||
const paperNumKeys: string[] = ['publication_year', 'volume', 'number'];
|
||||
appendToFilter('paper', paperStrKeys, paperNumKeys);
|
||||
}
|
||||
if (type === 'all' || type === 'url') {
|
||||
const urlStrKeys: string[] = ['title', 'keyword', 'url', 'file.original_file_name'];
|
||||
const urlNumKeys: string[] = [];
|
||||
appendToFilter('url', urlStrKeys, urlNumKeys);
|
||||
}
|
||||
if (type !== 'all') {
|
||||
filter['item_type_name'] = 'xnp' + type;
|
||||
}
|
||||
}
|
||||
const offset = condition.limit * (condition.page - 1);
|
||||
const result = items.chain().find(filter);
|
||||
const itemSorter = new ItemSorter(condition);
|
||||
const ret = {
|
||||
total: result.count(),
|
||||
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
static async getListByAdvancedSearchQuery(query: AdvancedSearchQuery, condition: SortCondition) {
|
||||
const items = await database.getItems();
|
||||
const filter: any = query.getSearchFilter();
|
||||
const offset = condition.limit * (condition.page - 1);
|
||||
const result = items.chain().find(filter);
|
||||
const itemSorter = new ItemSorter(condition);
|
||||
const ret = {
|
||||
total: result.count(),
|
||||
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export default ItemUtil;
|
||||
@@ -1,26 +0,0 @@
|
||||
const escape = (str: string) => {
|
||||
const replacer = (match: string) => {
|
||||
const replace: any = {
|
||||
'!': '%21',
|
||||
"'": '%27',
|
||||
'(': '%28',
|
||||
')': '%29',
|
||||
'~': '%7E',
|
||||
'%20': '+',
|
||||
'%00': '\x00'
|
||||
};
|
||||
return replace[match];
|
||||
}
|
||||
return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, replacer);
|
||||
}
|
||||
|
||||
const unescape = (str: string) => {
|
||||
return decodeURIComponent(str.replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
const Functions = {
|
||||
escape,
|
||||
unescape
|
||||
};
|
||||
|
||||
export default Functions;
|
||||
@@ -0,0 +1,15 @@
|
||||
const SITE_TITLE = 'Pupil Platform';
|
||||
const SITE_SLOGAN = 'Home';
|
||||
const GOOGLE_ANALYTICS_TRACKING_ID = '';
|
||||
const XOONIPS_ITEMTYPES = ['conference', 'paper', 'book', 'data', 'model', 'url'];
|
||||
|
||||
export type MultiLang = 'en' | 'ja';
|
||||
|
||||
const Config = {
|
||||
SITE_TITLE,
|
||||
SITE_SLOGAN,
|
||||
GOOGLE_ANALYTICS_TRACKING_ID,
|
||||
XOONIPS_ITEMTYPES,
|
||||
}
|
||||
|
||||
export default Config;
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import jsonp from 'jsonp';
|
||||
import React, { Component } from 'react';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import funcs from '../lib/Functions';
|
||||
import { MultiLang } from '../../config';
|
||||
import Functions from '../../functions';
|
||||
import styles from './FlickrBadge.module.css';
|
||||
|
||||
interface FlickrServicesFeedsPhotosPublicItem {
|
||||
@@ -40,6 +41,7 @@ const FlickrBadgeItem = (props: { item: FlickrServicesFeedsPhotosPublicItem }) =
|
||||
}
|
||||
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
tags: string;
|
||||
}
|
||||
interface State {
|
||||
@@ -123,7 +125,7 @@ class FlickrBadge extends Component<Props, State> {
|
||||
const url = 'https://www.flickr.com';
|
||||
const tags = this.props.tags.replace(',', ' ');
|
||||
const tagsLabel = this.props.tags.replace(',', ' and ');
|
||||
const tagsUrl = url + '/photos/tags/' + funcs.escape(tags);
|
||||
const tagsUrl = url + '/photos/tags/' + Functions.escape(tags);
|
||||
return (
|
||||
<div className={styles.flickr}>
|
||||
<div className={styles.badge}>
|
||||
@@ -6,20 +6,24 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.database :global(.list .listTable) {
|
||||
.database :global(.listTable) {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 5px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.database :global(.list .listTable .listIcon),
|
||||
.database :global(.list .listTable .listExtra) {
|
||||
.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%;
|
||||
}
|
||||
@@ -41,6 +45,20 @@
|
||||
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;
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import PageNotFound from '../common/lib/PageNotFound';
|
||||
import { 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';
|
||||
|
||||
interface Props {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
const Database = (props: Props) => {
|
||||
const { lang } = props;
|
||||
return (
|
||||
<div className={styles.database}>
|
||||
<Helmet>
|
||||
<title>{Functions.mlang('[en]Database[/en][ja]データベース[/ja]', lang)} - {Functions.siteTitle(lang)}</title>
|
||||
</Helmet>
|
||||
<Switch>
|
||||
<Route exact path="/database" render={(props: RouteComponentProps) => <DatabaseTop lang={lang} {...props} />} />
|
||||
<Route exact path="/database/item/:id" render={(props: RouteComponentProps<{ id: string; doi: string }>) => <DatabaseDetailItem lang={lang} {...props} />} />
|
||||
<Route exact path="/database/item/id/:doi" render={(props: RouteComponentProps<{ id: string; doi: string }>) => <DatabaseDetailItem lang={lang} {...props} />} />
|
||||
<Route exact path="/database/advanced" render={(props: RouteComponentProps) => <DatabaseAdvancedSearch lang={lang} {...props} />} />
|
||||
<Route exact path="/database/list" render={(props: RouteComponentProps<{ id: string }>) => <DatabaseSearchByIndexId lang={lang} {...props} />} />
|
||||
<Route exact path="/database/list/:id" render={(props: RouteComponentProps<{ id: string }>) => <DatabaseSearchByIndexId lang={lang} {...props} />} />
|
||||
<Route exact path="/database/search" render={(props: RouteComponentProps) => <DatabaseSearchByKeyword lang={lang} {...props} />} />
|
||||
<Route exact path="/database/search/advanced" render={(props: RouteComponentProps) => <DatabaseSearchByAdvancedKeyword lang={lang} {...props} />} />
|
||||
<Route exact path="/database/search/itemtype/:itemType" render={(props: RouteComponentProps<{ itemType: string; subItemType: string }>) => <DatabaseSearchByItemType lang={lang} {...props} />} />
|
||||
<Route exact path="/database/search/itemtype/:itemType/:subItemType" render={(props: RouteComponentProps<{ itemType: string; subItemType: string }>) => <DatabaseSearchByItemType lang={lang} {...props} />} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Database;
|
||||
@@ -1,16 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import Config, { MultiLang } from '../config';
|
||||
import Functions from '../functions';
|
||||
import ItemType from './item-type';
|
||||
import AdvancedSearchQuery from './lib/AdvancedSearchQuery';
|
||||
import ItemUtil from './lib/ItemUtil';
|
||||
|
||||
interface Props extends RouteComponentProps { }
|
||||
interface State { }
|
||||
interface Props extends RouteComponentProps {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
class DatabaseAdvancedSearch extends Component<Props, State> {
|
||||
class DatabaseAdvancedSearch extends Component<Props> {
|
||||
|
||||
private query: AdvancedSearchQuery = new AdvancedSearchQuery();
|
||||
private itemTypes = ['conference', 'paper', 'book', 'data', 'model', 'url'];
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -20,19 +22,20 @@ class DatabaseAdvancedSearch extends Component<Props, State> {
|
||||
handleClickSearchButton() {
|
||||
if (!this.query.empty()) {
|
||||
const url = ItemUtil.getSearchByAdvancedKeywordsUrl(this.query);
|
||||
this.props.history.push(url);
|
||||
this.props.history.push(url);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = this.props;
|
||||
return (
|
||||
<div className="advancedSearch">
|
||||
<h3>Search Items</h3>
|
||||
<h3>{Functions.mlang('[en]Search Items[/en][ja]アイテム検索[/ja]', lang)}</h3>
|
||||
<div className="search">
|
||||
<button className="formButton" onClick={this.handleClickSearchButton}>Search</button>
|
||||
</div>
|
||||
{this.itemTypes.map((type, i) => {
|
||||
return <ItemType.AdvancedSearch key={i} type={'xnp' + type} query={this.query} />;
|
||||
{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>
|
||||
@@ -0,0 +1,89 @@
|
||||
import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import Loading from '../common/lib/Loading';
|
||||
import PageNotFound from '../common/lib/PageNotFound';
|
||||
import { MultiLang } from '../config';
|
||||
import Functions from '../functions';
|
||||
import ItemType from './item-type';
|
||||
import ItemUtil, { Item } from './lib/ItemUtil';
|
||||
|
||||
interface Params {
|
||||
id: string;
|
||||
doi: string;
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<Params> {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
interface State {
|
||||
loading: boolean;
|
||||
item: Item | null;
|
||||
}
|
||||
|
||||
class DatabaseDetailItem extends Component<Props, State> {
|
||||
|
||||
private id: number;
|
||||
private doi: string;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
item: null,
|
||||
};
|
||||
const { params } = this.props.match;
|
||||
this.id = typeof params.id !== 'undefined' ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : 0;
|
||||
this.doi = typeof params.doi !== 'undefined' ? params.doi : '';
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateItem();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { params } = this.props.match;
|
||||
const id = typeof params.id !== 'undefined' ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : 0;
|
||||
const doi = typeof params.doi !== 'undefined' ? params.doi : '';
|
||||
if (this.id !== id || this.doi !== doi) {
|
||||
this.id = id;
|
||||
this.doi = doi;
|
||||
this.updateItem();
|
||||
}
|
||||
}
|
||||
|
||||
updateItem() {
|
||||
if (this.doi !== '') {
|
||||
ItemUtil.getByDoi(this.doi, (item) => {
|
||||
this.setState({ loading: false, item });
|
||||
});
|
||||
} else if (this.id !== 0) {
|
||||
ItemUtil.get(this.id, (item) => {
|
||||
this.setState({ loading: false, item });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseDetailItem;
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { MultiLang } from '../config';
|
||||
import AdvancedSearchQuery from './lib/AdvancedSearchQuery';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
lang: MultiLang
|
||||
}
|
||||
|
||||
interface State {
|
||||
search: string;
|
||||
query: AdvancedSearchQuery;
|
||||
}
|
||||
|
||||
class DatabaseSearchByAdvancedKeyword extends 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() {
|
||||
return ItemUtil.getSearchByAdvancedKeywordsUrl(this.state.query);
|
||||
}
|
||||
|
||||
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
|
||||
ItemUtil.getListByAdvancedSearchQuery(this.state.query, condition, func);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = this.props;
|
||||
const baseUrl = this.getUrl();
|
||||
return (
|
||||
<div className="list">
|
||||
<h3>Listing item</h3>
|
||||
<DatabaseListItem lang={lang} url={baseUrl} search={this.searchFunc} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByAdvancedKeyword;
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PageNotFound from '../common/lib/PageNotFound';
|
||||
import { MultiLang } from '../config';
|
||||
import Functions from '../functions';
|
||||
import DatabaseListIndex from './lib/DatabaseListIndex';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
import IndexUtil, { Index, INDEX_ID_PUBLIC } from './lib/IndexUtil';
|
||||
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
|
||||
|
||||
interface Params {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface Props extends RouteComponentProps<Params> {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
interface State {
|
||||
indexId: number;
|
||||
}
|
||||
|
||||
class DatabaseSearchByIndexId extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { params } = props.match;
|
||||
this.state = {
|
||||
indexId: params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : INDEX_ID_PUBLIC,
|
||||
}
|
||||
this.searchFunc = this.searchFunc.bind(this);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
const { params } = nextProps.match;
|
||||
const indexId = params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : 0) : INDEX_ID_PUBLIC;
|
||||
if (prevState.indexId !== indexId) {
|
||||
return { indexId };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
if (this.state.indexId === 0) {
|
||||
return '/';
|
||||
}
|
||||
return IndexUtil.getUrl(this.state.indexId);
|
||||
}
|
||||
|
||||
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
|
||||
if (this.state.indexId === 0) {
|
||||
const res = { total: 0, data: [] };
|
||||
func(res);
|
||||
} else {
|
||||
ItemUtil.getListByIndexId(this.state.indexId, condition, func);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = this.props;
|
||||
const index = IndexUtil.get(this.state.indexId);
|
||||
if (index === null) {
|
||||
return <PageNotFound lang={lang} />;
|
||||
}
|
||||
const baseUrl = this.getUrl();
|
||||
const pIndexes = IndexUtil.getParents(this.state.indexId);
|
||||
const parents = pIndexes.map((value: Index) => {
|
||||
const url: string = IndexUtil.getUrl(value.id);
|
||||
return <Fragment key={value.id}>/ <Link to={url}>{value.title}</Link> </Fragment>;
|
||||
});
|
||||
const title = pIndexes.map((value) => { return '/' + value.title; }).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} />
|
||||
<DatabaseListItem lang={lang} url={baseUrl} search={this.searchFunc} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByIndexId;
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { MultiLang } from '../config';
|
||||
import Functions from '../functions';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
import ItemUtil, { SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
|
||||
|
||||
interface Params {
|
||||
itemType: string;
|
||||
subItemType: string;
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<Params> {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
interface State {
|
||||
itemType: string;
|
||||
subItemType: string;
|
||||
}
|
||||
|
||||
class DatabaseSearchByItemType extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { params } = this.props.match;
|
||||
this.state = {
|
||||
itemType: params.itemType ? params.itemType : '',
|
||||
subItemType: params.subItemType ? params.subItemType : '',
|
||||
};
|
||||
this.searchFunc = this.searchFunc.bind(this);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
const { params } = nextProps.match;
|
||||
const item_type = params.itemType ? params.itemType : '';
|
||||
const sub_item_type = params.subItemType ? params.subItemType : '';
|
||||
if (prevState.itemType !== item_type || prevState.subItemType !== sub_item_type) {
|
||||
return { item_type, sub_item_type };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
|
||||
if (this.state.itemType === '') {
|
||||
const res = { total: 0, data: [] };
|
||||
func(res);
|
||||
} else {
|
||||
ItemUtil.getListByItemType(this.state.itemType, this.state.subItemType, condition, func);
|
||||
}
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
let url = ItemUtil.getItemTypeSearchUrl(this.state.itemType);
|
||||
if (this.state.subItemType !== '') {
|
||||
url += '/' + this.state.subItemType;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = 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} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByItemType;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { MultiLang } from '../config';
|
||||
import Functions from '../functions';
|
||||
import DatabaseListItem from './lib/DatabaseListItem';
|
||||
import ItemUtil, { KeywordSearchType, SearchCallbackFunc, SortCondition } from './lib/ItemUtil';
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
lang: MultiLang;
|
||||
}
|
||||
|
||||
interface State {
|
||||
type: KeywordSearchType;
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
class DatabaseSearchByKeyword extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { type, keyword } = ItemUtil.getSearchKeywordByQuery(this.props.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() {
|
||||
return ItemUtil.getSearchByKeywordUrl(this.state.type, this.state.keyword);
|
||||
}
|
||||
|
||||
searchFunc(condition: SortCondition, func: SearchCallbackFunc) {
|
||||
if (this.state.keyword === '') {
|
||||
const res = { total: 0, data: [] };
|
||||
func(res);
|
||||
} else {
|
||||
ItemUtil.getListByKeyword(this.state.type, this.state.keyword, condition, func);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lang } = 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} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabaseSearchByKeyword;
|
||||