add page loading action.

This commit is contained in:
Yoshihiro OKUMURA
2019-05-20 22:57:35 +09:00
parent c62c1007ce
commit d0098db1b0
12 changed files with 107 additions and 84 deletions
+1
View File
@@ -24,6 +24,7 @@
"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"
},
+6
View File
@@ -0,0 +1,6 @@
.loading {
display: flex;
justify-content: center;
align-content: center;
margin: 100px 0 0 0;
}
+13
View File
@@ -0,0 +1,13 @@
import React from 'react';
import Spinner from 'react-spinner-material';
import styles from './Loading.module.css';
const Loading = () => {
return (
<div className={styles.loading}>
<Spinner size={70} spinnerColor={'#cccccc'} spinnerWidth={8} visible={true} />
</div>
);
}
export default Loading;
+3 -3
View File
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import Helmet from 'react-helmet';
import { RouteComponentProps } from 'react-router';
import { RouteComponentProps, withRouter } from 'react-router';
interface Props extends RouteComponentProps { }
@@ -33,7 +33,7 @@ class PageNotFound extends Component<Props> {
</Helmet>
<h1>Page Not Found</h1>
<section>
<p>The page you were trying to access doesn't exist or has been removed.</p>
<p>The page you were trying to access doesn't exist.</p>
<p>If the page does not automatically reload, please click <a href={this.url}>here</a></p>
</section>
</div>
@@ -41,4 +41,4 @@ class PageNotFound extends Component<Props> {
}
}
export default PageNotFound;
export default withRouter(PageNotFound);
+30 -29
View File
@@ -3,63 +3,64 @@ 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 {
id: number;
doi: string;
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.state = {
id: typeof params.id !== 'undefined' ? parseInt(params.id, 10) : 0,
doi: typeof params.doi !== 'undefined' ? params.doi : '',
item: null,
}
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;
const id = typeof params.id !== 'undefined' ? parseInt(params.id, 10) : 0;
const doi = typeof params.doi !== 'undefined' ? params.doi : '';
if (doi !== '') {
ItemUtil.getByDoi(this.state.doi, (item) => {
this.setState({ item });
});
} else if (id !== 0) {
ItemUtil.get(this.state.id, (item) => {
this.setState({ item });
});
}
this.setState({ id, doi });
this.updateItem(id, doi);
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(this.state.id, this.state.doi);
this.updateItem();
}
updateItem(id: number, doi: string) {
if (doi !== '') {
ItemUtil.getByDoi(doi, (item) => {
this.setState({ item });
updateItem() {
if (this.doi !== '') {
ItemUtil.getByDoi(this.doi, (item) => {
this.setState({ loading: false, item });
});
} else if (id !== 0) {
ItemUtil.get(id, (item) => {
this.setState({ item });
} else if (this.id !== 0) {
ItemUtil.get(this.id, (item) => {
this.setState({ loading: false, item });
});
} else {
this.setState({ loading: false });
}
}
render() {
if (this.state.loading) {
return <Loading />;
}
if (this.state.item === null) {
return null;
return <PageNotFound />;
}
return (
<>
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
import AdvancedSearchQuery from './lib/AdvancedSearchQuery';
import ItemUtil, { SortCondition, SearchCallbackFunc } from './lib/ItemUtil';
import ItemUtil, { SortCondition } from './lib/ItemUtil';
import DatabaseListItem from './lib/DatabaseListItem';
interface Props extends RouteComponentProps { }
@@ -28,9 +28,8 @@ class DatabaseSearchByAdvancedKeyword extends Component<Props, State> {
return ItemUtil.getSearchByAdvancedKeywordsUrl(this.state.query);
}
async search(condition: SortCondition, callback: SearchCallbackFunc) {
const items = ItemUtil.getListByAdvancedSearchQuery(this.state.query, condition, callback);
return items;
async search(condition: SortCondition) {
return ItemUtil.getListByAdvancedSearchQuery(this.state.query, condition);
}
render() {
@@ -3,9 +3,10 @@ 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, SearchCallbackFunc } from './lib/ItemUtil';
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 }> { }
@@ -18,18 +19,19 @@ class DatabaseSearchByIndexId extends Component<Props, State> {
constructor(props: Props) {
super(props);
const { params } = this.props.match;
const indexId = params.id ? parseInt(params.id, 10) : INDEX_ID_PUBLIC;
const indexId = params.id ? (params.id.match(/^\d+$/) !== null ? parseInt(params.id, 10) : null) : INDEX_ID_PUBLIC;
this.state = {
index: IndexUtil.get(indexId),
index: indexId ? IndexUtil.get(indexId) : null,
}
this.search = this.search.bind(this);
}
componentWillReceiveProps(nextProps: Props) {
const { params } = nextProps.match;
const indexId = params.id ? parseInt(params.id, 10) : INDEX_ID_PUBLIC;
const index = IndexUtil.get(indexId);
this.setState({ index });
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() {
@@ -39,17 +41,16 @@ class DatabaseSearchByIndexId extends Component<Props, State> {
return IndexUtil.getUrl(this.state.index.id);
}
async search(condition: SortCondition, callback: SearchCallbackFunc) {
async search(condition: SortCondition) {
if (this.state.index === null) {
return { total: 0, data: [] };
}
const items = ItemUtil.getListByIndexId(this.state.index.id, condition, callback);
return items;
return ItemUtil.getListByIndexId(this.state.index.id, condition);
}
render() {
if (this.state.index === null) {
return null;
return <PageNotFound />;
}
const baseUrl = this.getUrl();
const pIndexes = IndexUtil.getParents(this.state.index.id);
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
import ItemUtil, { SortCondition, SearchCallbackFunc } from './lib/ItemUtil';
import ItemUtil, { SortCondition } from './lib/ItemUtil';
import DatabaseListItem from './lib/DatabaseListItem';
interface Props extends RouteComponentProps<{ itemType: string, subItemType: string }> { }
@@ -29,12 +29,11 @@ class DatabaseSearchByItemType extends Component<Props, State> {
this.setState({ item_type, sub_item_type });
}
async search(condition: SortCondition, callback: SearchCallbackFunc) {
async search(condition: SortCondition) {
if (this.state.item_type === '') {
return { total: 0, data: [] };
}
const items = ItemUtil.getListByItemType(this.state.item_type, this.state.sub_item_type, condition, callback);
return items;
return ItemUtil.getListByItemType(this.state.item_type, this.state.sub_item_type, condition);
}
getUrl() {
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
import ItemUtil, { SearchByKeywordType, SortCondition, SearchCallbackFunc } from './lib/ItemUtil';
import ItemUtil, { SearchByKeywordType, SortCondition } from './lib/ItemUtil';
import DatabaseListItem from './lib/DatabaseListItem';
interface Props extends RouteComponentProps { }
@@ -15,7 +15,7 @@ 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.state = { type, keyword };
this.search = this.search.bind(this);
}
@@ -28,12 +28,11 @@ class DatabaseSearchByKeyword extends Component<Props, State> {
return ItemUtil.getSearchByKeywordUrl(this.state.type, this.state.keyword);
}
async search(condition: SortCondition, callback: SearchCallbackFunc) {
async search(condition: SortCondition) {
if (this.state.keyword === '') {
return { total: 0, data: [] };
}
const items = ItemUtil.getListByKeyword(this.state.type, this.state.keyword, condition, callback);
return items;
return ItemUtil.getListByKeyword(this.state.type, this.state.keyword, condition);
}
render() {
@@ -2,9 +2,10 @@ import React, { Component, ChangeEvent } from 'react';
import { RouteComponentProps } from 'react-router';
import { Link, withRouter } from 'react-router-dom';
import qs from 'query-string';
import { Item, SortConditionLimit, SortConditionOrderBy, SortConditionOrderDir, SortCondition, SearchResult, SearchCallbackFunc } from './ItemUtil';
import { Item, SortConditionLimit, SortConditionOrderBy, SortConditionOrderDir, SortCondition, SearchResult } from './ItemUtil';
import ItemType from '../item-type';
import styles from './DatabaseListItem.module.css';
import Loading from '../../Loading';
const SORT_CONDITION_DEFAULT: SortCondition = {
limit: 20,
@@ -16,7 +17,7 @@ const SORT_CONDITION_RANGE_ORDER_BY = ['title', 'doi', 'last_update_date', 'crea
const SORT_CONDITION_RANGE_ORDER_DIR = ['0', '1'];
const SORT_CONDITION_RANGE_LIMIT = ['20', '50', '100'];
export interface SearchFunc { (condition: SortCondition, callback: SearchCallbackFunc): Promise<SearchResult> }
export interface SearchFunc { (condition: SortCondition): Promise<SearchResult> }
interface Props extends RouteComponentProps {
url: string;
@@ -24,40 +25,41 @@ interface Props extends RouteComponentProps {
}
interface State {
url: string;
search: SearchFunc;
loading: boolean;
condition: SortCondition;
result: SearchResult;
}
class DatabaseListItem extends Component<Props, State> {
private url: string;
private search: SearchFunc;
constructor(props: Props) {
super(props);
this.state = {
url: props.url,
search: props.search,
loading: true,
condition: this.getSortConditionByQuery(this.props.location.search),
result: { total: 0, data: [] }
};
this.url = props.url;
this.search = props.search;
this.handleSelectOrderby = this.handleSelectOrderby.bind(this);
this.handleSelectItemcount = this.handleSelectItemcount.bind(this);
}
componentWillReceiveProps(nextProps: Props) {
const url = nextProps.url;
const search = nextProps.search;
async componentWillReceiveProps(nextProps: Props) {
this.url = nextProps.url;
this.search = nextProps.search;
const condition = this.getSortConditionByQuery(nextProps.location.search);
this.setState({ url, search, condition });
search(condition, (result) => {
this.setState({ result });
});
this.setState({ loading: true });
const result = await this.search(condition);
this.setState({ loading: false, result, condition });
}
componentDidMount() {
this.state.search(this.state.condition, (result) => {
this.setState({ result });
});
async componentDidMount() {
const result = await this.search(this.state.condition);
this.setState({ loading: false, result });
}
handleSelectOrderby(event: ChangeEvent<HTMLSelectElement>) {
@@ -85,8 +87,8 @@ class DatabaseListItem extends Component<Props, State> {
params.push('order_dir=' + String(newOrderDir));
params.push('itemcount=' + String(newLimit));
params.push('page=' + String(newPage));
const join = this.state.url.indexOf('?') < 0 ? '?' : '&'
return (params.length > 0 ? this.state.url + join + params.join('&') : this.state.url);
const join = this.url.indexOf('?') < 0 ? '?' : '&'
return (params.length > 0 ? this.url + join + params.join('&') : this.url);
}
getDefaultSortCondition() {
@@ -183,6 +185,9 @@ class DatabaseListItem extends Component<Props, State> {
}
render() {
if (this.state.loading) {
return <Loading />;
}
const result = this.state.result;
if (result.data.length === 0) {
return (
+4 -10
View File
@@ -177,8 +177,6 @@ export interface SearchResult {
data: Item[];
}
export interface SearchCallbackFunc { (result: SearchResult): void }
class ItemDatabase {
private db: loki;
private items: Collection<any> | null = null;
@@ -334,7 +332,7 @@ class ItemUtil {
return item;
}
static async getListByIndexId(indexId: number, condition: SortCondition, callback: SearchCallbackFunc) {
static async getListByIndexId(indexId: number, condition: SortCondition) {
const items = await database.getItems();
const filter = {
'index.index_id': indexId
@@ -346,11 +344,10 @@ class ItemUtil {
total: result.count(),
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
};
callback(ret);
return ret;
}
static async getListByItemType(itemType: string, subItemType: string, condition: SortCondition, callback: SearchCallbackFunc) {
static async getListByItemType(itemType: string, subItemType: string, condition: SortCondition) {
const items = await database.getItems();
let filter: any = {
item_type_name: 'xnp' + itemType,
@@ -377,11 +374,10 @@ class ItemUtil {
total: result.count(),
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
};
callback(ret);
return ret;
}
static async getListByKeyword(type: SearchByKeywordType, keyword: string, condition: SortCondition, callback: SearchCallbackFunc) {
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;
@@ -454,11 +450,10 @@ class ItemUtil {
total: result.count(),
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
};
callback(ret);
return ret;
}
static async getListByAdvancedSearchQuery(query: AdvancedSearchQuery, condition: SortCondition, callback: SearchCallbackFunc) {
static async getListByAdvancedSearchQuery(query: AdvancedSearchQuery, condition: SortCondition) {
const items = await database.getItems();
const filter: any = query.getSearchFilter();
const offset = condition.limit * (condition.page - 1);
@@ -468,7 +463,6 @@ class ItemUtil {
total: result.count(),
data: result.sort(itemSorter.sort).offset(offset).limit(condition.limit).data()
};
callback(ret);
return ret;
}
}
+5
View File
@@ -8372,6 +8372,11 @@ react-side-effect@^1.1.0:
exenv "^1.2.1"
shallowequal "^1.0.1"
react-spinner-material@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/react-spinner-material/-/react-spinner-material-1.1.1.tgz#b996d893251dd82ec12e21e03a28af100df05cab"
integrity sha512-zoCsu4hmL2Z6Ihm6aI40+FKUUQ6ckMK2FvAwYpHd9xdc7Q9V4OiDlbDfH5XgxZUZuB/tAjvDaC+dbRexEw2D2A==
react-transition-group@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.0.1.tgz#8cb8d58763e259da465385bb83b3b41b3ecba629"