From d0098db1b0b77ed2b6f45dc4f2c678c3d83141bc Mon Sep 17 00:00:00 2001 From: Yoshihiro OKUMURA Date: Mon, 20 May 2019 22:57:35 +0900 Subject: [PATCH] add page loading action. --- package.json | 1 + src/components/Loading.module.css | 6 ++ src/components/Loading.tsx | 13 ++++ src/components/PageNotFound.tsx | 6 +- .../database/DatabaseDetailItem.tsx | 59 ++++++++++--------- .../DatabaseSearchByAdvancedKeyword.tsx | 7 +-- .../database/DatabaseSearchByIndexId.tsx | 21 +++---- .../database/DatabaseSearchByItemType.tsx | 7 +-- .../database/DatabaseSearchByKeyword.tsx | 9 ++- .../database/lib/DatabaseListItem.tsx | 43 ++++++++------ src/components/database/lib/ItemUtil.ts | 14 ++--- yarn.lock | 5 ++ 12 files changed, 107 insertions(+), 84 deletions(-) create mode 100644 src/components/Loading.module.css create mode 100644 src/components/Loading.tsx diff --git a/package.json b/package.json index 7c683b1..ea6c2fc 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/components/Loading.module.css b/src/components/Loading.module.css new file mode 100644 index 0000000..439c5f5 --- /dev/null +++ b/src/components/Loading.module.css @@ -0,0 +1,6 @@ +.loading { + display: flex; + justify-content: center; + align-content: center; + margin: 100px 0 0 0; +} diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx new file mode 100644 index 0000000..e55881e --- /dev/null +++ b/src/components/Loading.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import Spinner from 'react-spinner-material'; +import styles from './Loading.module.css'; + +const Loading = () => { + return ( +
+ +
+ ); +} + +export default Loading; \ No newline at end of file diff --git a/src/components/PageNotFound.tsx b/src/components/PageNotFound.tsx index 010e285..38cc6ff 100644 --- a/src/components/PageNotFound.tsx +++ b/src/components/PageNotFound.tsx @@ -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 {

Page Not Found

-

The page you were trying to access doesn't exist or has been removed.

+

The page you were trying to access doesn't exist.

If the page does not automatically reload, please click here

@@ -41,4 +41,4 @@ class PageNotFound extends Component { } } -export default PageNotFound; \ No newline at end of file +export default withRouter(PageNotFound); \ No newline at end of file diff --git a/src/components/database/DatabaseDetailItem.tsx b/src/components/database/DatabaseDetailItem.tsx index 4d99d2f..f9ab4a7 100644 --- a/src/components/database/DatabaseDetailItem.tsx +++ b/src/components/database/DatabaseDetailItem.tsx @@ -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 { + 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 ; + } if (this.state.item === null) { - return null; + return ; } return ( <> diff --git a/src/components/database/DatabaseSearchByAdvancedKeyword.tsx b/src/components/database/DatabaseSearchByAdvancedKeyword.tsx index dc3ce53..2992b9d 100644 --- a/src/components/database/DatabaseSearchByAdvancedKeyword.tsx +++ b/src/components/database/DatabaseSearchByAdvancedKeyword.tsx @@ -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 { 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() { diff --git a/src/components/database/DatabaseSearchByIndexId.tsx b/src/components/database/DatabaseSearchByIndexId.tsx index e19bf17..593a1d2 100644 --- a/src/components/database/DatabaseSearchByIndexId.tsx +++ b/src/components/database/DatabaseSearchByIndexId.tsx @@ -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 { 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 { 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 ; } const baseUrl = this.getUrl(); const pIndexes = IndexUtil.getParents(this.state.index.id); diff --git a/src/components/database/DatabaseSearchByItemType.tsx b/src/components/database/DatabaseSearchByItemType.tsx index 3518681..a634dbc 100644 --- a/src/components/database/DatabaseSearchByItemType.tsx +++ b/src/components/database/DatabaseSearchByItemType.tsx @@ -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 { 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() { diff --git a/src/components/database/DatabaseSearchByKeyword.tsx b/src/components/database/DatabaseSearchByKeyword.tsx index 93b415c..cc1641b 100644 --- a/src/components/database/DatabaseSearchByKeyword.tsx +++ b/src/components/database/DatabaseSearchByKeyword.tsx @@ -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 { 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 { 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() { diff --git a/src/components/database/lib/DatabaseListItem.tsx b/src/components/database/lib/DatabaseListItem.tsx index aae16b5..15ba262 100644 --- a/src/components/database/lib/DatabaseListItem.tsx +++ b/src/components/database/lib/DatabaseListItem.tsx @@ -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 } +export interface SearchFunc { (condition: SortCondition): Promise } 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 { + 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) { @@ -85,8 +87,8 @@ class DatabaseListItem extends Component { 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 { } render() { + if (this.state.loading) { + return ; + } const result = this.state.result; if (result.data.length === 0) { return ( diff --git a/src/components/database/lib/ItemUtil.ts b/src/components/database/lib/ItemUtil.ts index 9951355..764cfe4 100644 --- a/src/components/database/lib/ItemUtil.ts +++ b/src/components/database/lib/ItemUtil.ts @@ -177,8 +177,6 @@ export interface SearchResult { data: Item[]; } -export interface SearchCallbackFunc { (result: SearchResult): void } - class ItemDatabase { private db: loki; private items: Collection | 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; } } diff --git a/yarn.lock b/yarn.lock index 949106f..4286370 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"