Преглед изворни кода

Merge pull request #495 from smiclea/pagination

Add pagination to lists (replicas, endpoints etc)
Nashwan Azhari пре 6 година
родитељ
комит
8d2bd22d8f

+ 2 - 0
config.js

@@ -93,6 +93,8 @@ const conf: Config = {
   // If the field doesn't contain `password` in its name, the following list will be used instead
   passwordFields: ['private_key_passphrase', 'secret_access_key'],
 
+  // The number of items per page applicable to main lists: replicas, migrations, endpoints, users etc.
+  mainListItemsPerPage: 20,
 }
 
 export const config = conf

+ 109 - 0
src/components/atoms/Pagination/Pagination.jsx

@@ -0,0 +1,109 @@
+/*
+Copyright (C) 2020  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { observer } from 'mobx-react'
+import styled, { css } from 'styled-components'
+
+import Arrow from '../../atoms/Arrow'
+import HorizontalLoading from '../../atoms/HorizontalLoading'
+
+import StyleProps from '../../styleUtils/StyleProps'
+import Palette from '../../styleUtils/Palette'
+
+const Wrapper = styled.div`
+  display: flex;
+  justify-content: center;
+  flex-shrink: 0;
+`
+const pageStyle = css`
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: ${Palette.grayscale[1]};
+`
+const pageButtonStyle = css`
+  width: 32px;
+  height: 30px;
+  cursor: ${props => props.disabled ? 'default' : 'pointer'};
+  padding-top: 2px;
+`
+const PagePrevious = styled.div`
+  border-top-left-radius: ${StyleProps.borderRadius};
+  border-bottom-left-radius: ${StyleProps.borderRadius};
+  ${pageStyle}
+  ${pageButtonStyle}
+`
+const PageNext = styled.div`
+  border-top-right-radius: ${StyleProps.borderRadius};
+  border-bottom-right-radius: ${StyleProps.borderRadius};
+  ${pageStyle}
+  ${pageButtonStyle}
+`
+const PageNumber = styled.div`
+  width: 64px;
+  height: 29px;
+  flex-direction: column;
+  margin: 0 1px;
+  padding-top: 3px;
+  ${pageStyle}
+`
+
+type Props = {
+  className?: string,
+  style?: any,
+  previousDisabled: boolean,
+  onPreviousClick: () => void,
+  currentPage: number,
+  totalPages: number,
+  loading?: boolean,
+  nextDisabled: boolean,
+  onNextClick: () => void,
+}
+
+@observer
+class Pagination extends React.Component<Props> {
+  render() {
+    return (
+      <Wrapper
+        className={this.props.className}
+        style={this.props.style}
+        onMouseDown={e => { e.preventDefault() }}
+      >
+        <PagePrevious
+          disabled={this.props.previousDisabled}
+          onClick={() => { if (!this.props.previousDisabled) { this.props.onPreviousClick() } }}
+        >
+          <Arrow orientation="left" disabled={this.props.previousDisabled} color={Palette.black} thick />
+        </PagePrevious>
+        <PageNumber>
+          {this.props.currentPage} of {this.props.totalPages}
+          {this.props.loading ? (
+            <HorizontalLoading style={{ width: '100%', top: '3px' }} />
+          ) : null}
+        </PageNumber>
+        <PageNext
+          onClick={() => { if (!this.props.nextDisabled) { this.props.onNextClick() } }}
+          disabled={this.props.nextDisabled}
+        >
+          <Arrow disabled={this.props.nextDisabled} color={Palette.black} thick />
+        </PageNext>
+      </Wrapper>
+    )
+  }
+}
+
+export default Pagination

+ 6 - 0
src/components/atoms/Pagination/package.json

@@ -0,0 +1,6 @@
+{
+  "name": "Pagination",
+  "version": "0.0.0",
+  "private": true,
+  "main": "./Pagination.jsx"
+}

+ 52 - 5
src/components/organisms/FilterList/FilterList.jsx

@@ -18,13 +18,16 @@ import * as React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 
-import type { MainItem } from '../../../types/MainItem'
 import MainListFilter from '../../molecules/MainListFilter'
-
+import Pagination from '../../atoms/Pagination'
 import type { Action as DropdownAction } from '../../molecules/ActionDropdown'
 import type { ItemComponentProps } from '../../organisms/MainList'
 import MainList from '../../organisms/MainList'
 
+import type { MainItem } from '../../../types/MainItem'
+
+import configLoader from '../../../utils/Config'
+
 const Wrapper = styled.div`
   display: flex;
   flex-direction: column;
@@ -57,6 +60,7 @@ type State = {
   filterText: string,
   selectedItems: any[],
   selectAllSelected: boolean,
+  currentPage: number,
 }
 @observer
 class FilterList extends React.Component<Props, State> {
@@ -66,6 +70,18 @@ class FilterList extends React.Component<Props, State> {
     filterText: '',
     selectedItems: [],
     selectAllSelected: false,
+    currentPage: 1,
+  }
+
+  get paginatedItems() {
+    let paginatedItems = this.state.items
+    if (paginatedItems.length > configLoader.config.mainListItemsPerPage) {
+      paginatedItems = this.state.items.filter((_, i) =>
+        i < (configLoader.config.mainListItemsPerPage * this.state.currentPage) &&
+        i >= (configLoader.config.mainListItemsPerPage * (this.state.currentPage - 1))
+      )
+    }
+    return paginatedItems
   }
 
   componentWillMount() {
@@ -79,12 +95,15 @@ class FilterList extends React.Component<Props, State> {
         filterStatus: 'all',
         filterText: '',
         selectedItems: [],
+        currentPage: 1,
       })
       this.props.onSelectedItemsChange && this.props.onSelectedItemsChange([])
       return
     }
 
-    this.setState({ items: this.filterItems(props.items) })
+    this.setState({
+      items: this.filterItems(props.items),
+    })
   }
 
   handleFilterItemClick(item: DictItem) {
@@ -103,6 +122,7 @@ class FilterList extends React.Component<Props, State> {
       selectAllSelected,
       filterStatus: item.value,
       items,
+      currentPage: 1,
     }, () => {
       this.props.onSelectedItemsChange && this.props.onSelectedItemsChange(selectedItems)
     })
@@ -112,8 +132,10 @@ class FilterList extends React.Component<Props, State> {
     this.setState({
       filterText: text,
       items: this.filterItems(this.props.items, null, text),
+      currentPage: 1,
     })
   }
+
   handleItemSelectedChange(item: MainItem, selected: boolean) {
     let items = this.state.selectedItems.slice(0)
     let selectedItems = items.filter(i => item.id !== i.id) || []
@@ -130,7 +152,7 @@ class FilterList extends React.Component<Props, State> {
   handleSelectAllChange(selected: boolean) {
     let selectedItems = []
     if (selected) {
-      selectedItems = this.state.items.slice(0)
+      selectedItems = this.paginatedItems.slice(0)
     }
 
     this.setState({ selectedItems, selectAllSelected: selected }, () => {
@@ -148,6 +170,30 @@ class FilterList extends React.Component<Props, State> {
     return filteredItems
   }
 
+  handlePageClick(page: number) {
+    this.setState({ currentPage: page })
+  }
+
+  renderPagination() {
+    let itemsCount = this.state.items.length
+    let totalPages = Math.ceil(itemsCount / configLoader.config.mainListItemsPerPage)
+    let hasNextPage = this.state.currentPage * configLoader.config.mainListItemsPerPage < itemsCount
+    let isPreviousDisabled = this.state.currentPage === 1
+    let isNextDisabled = !hasNextPage
+
+    return itemsCount > configLoader.config.mainListItemsPerPage ? (
+      <Pagination
+        currentPage={this.state.currentPage}
+        totalPages={totalPages}
+        nextDisabled={isNextDisabled}
+        previousDisabled={isPreviousDisabled}
+        onNextClick={() => { this.handlePageClick(this.state.currentPage + 1) }}
+        onPreviousClick={() => { this.handlePageClick(this.state.currentPage - 1) }}
+        style={{ margin: '32px 0 16px 0' }}
+      />
+    ) : null
+  }
+
   render() {
     return (
       <Wrapper>
@@ -171,7 +217,7 @@ class FilterList extends React.Component<Props, State> {
         />
         <MainList
           loading={this.props.loading}
-          items={this.state.items}
+          items={this.paginatedItems}
           selectedItems={this.state.selectedItems}
           onSelectedChange={(item, selected) => { this.handleItemSelectedChange(item, selected) }}
           onItemClick={this.props.onItemClick}
@@ -184,6 +230,7 @@ class FilterList extends React.Component<Props, State> {
           onEmptyListButtonClick={this.props.onEmptyListButtonClick}
           data-test-id="filterList-mainList"
         />
+        {this.renderPagination()}
       </Wrapper>
     )
   }

+ 12 - 64
src/components/organisms/WizardInstances/WizardInstances.jsx

@@ -16,16 +16,15 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import { observer } from 'mobx-react'
-import styled, { css } from 'styled-components'
+import styled from 'styled-components'
 
 import Checkbox from '../../atoms/Checkbox'
 import ReloadButton from '../../atoms/ReloadButton'
-import Arrow from '../../atoms/Arrow'
-import HorizontalLoading from '../../atoms/HorizontalLoading'
 import StatusImage from '../../atoms/StatusImage'
 import Button from '../../atoms/Button'
 import SearchInput from '../../molecules/SearchInput'
 import InfoIcon from '../../atoms/InfoIcon'
+import Pagination from '../../atoms/Pagination'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -123,45 +122,6 @@ const SelectionInfo = styled.div``
 const FilterSeparator = styled.div`
   margin: 0 14px 0 16px;
 `
-const Pagination = styled.div`
-  display: flex;
-  justify-content: center;
-  margin: 32px 0 16px 0;
-  flex-shrink: 0;
-`
-const pageStyle = css`
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  background: ${Palette.grayscale[1]};
-`
-const pageButtonStyle = css`
-  width: 32px;
-  height: 30px;
-  cursor: ${props => props.disabled ? 'default' : 'pointer'};
-  padding-top: 2px;
-`
-
-const PagePrevious = styled.div`
-  border-top-left-radius: ${StyleProps.borderRadius};
-  border-bottom-left-radius: ${StyleProps.borderRadius};
-  ${pageStyle}
-  ${pageButtonStyle}
-`
-const PageNext = styled.div`
-  border-top-right-radius: ${StyleProps.borderRadius};
-  border-bottom-right-radius: ${StyleProps.borderRadius};
-  ${pageStyle}
-  ${pageButtonStyle}
-`
-const PageNumber = styled.div`
-  width: 64px;
-  height: 29px;
-  flex-direction: column;
-  margin: 0 1px;
-  padding-top: 3px;
-  ${pageStyle}
-`
 const Reloading = styled.div`
   margin: 32px auto 0 auto;
   flex-grow: 1;
@@ -402,28 +362,16 @@ class WizardInstances extends React.Component<Props, State> {
     let isNextDisabled = !hasNextPage || areAllDisabled
 
     return (
-      <Pagination onMouseDown={e => { e.preventDefault() }}>
-        <PagePrevious
-          disabled={isPreviousDisabled}
-          onClick={() => { if (!isPreviousDisabled) { this.handlePreviousPageClick() } }}
-          data-test-id="wInstances-prevPageButton"
-        >
-          <Arrow orientation="left" disabled={isPreviousDisabled} color={Palette.black} thick />
-        </PagePrevious>
-        <PageNumber data-test-id="wInstances-currentPage">
-          {this.props.currentPage} of {Math.ceil(this.props.instances.length / this.props.instancesPerPage)}
-          {this.props.chunksLoading ? (
-            <HorizontalLoading style={{ width: '100%', top: '3px' }} data-test-id="wInstances-loadingChunks" />
-          ) : null}
-        </PageNumber>
-        <PageNext
-          onClick={() => { if (!isNextDisabled) { this.handleNextPageClick() } }}
-          disabled={isNextDisabled}
-          data-test-id="wInstances-nextPageButton"
-        >
-          <Arrow disabled={isNextDisabled} color={Palette.black} thick />
-        </PageNext>
-      </Pagination>
+      <Pagination
+        style={{ margin: '32px 0 16px 0' }}
+        previousDisabled={isPreviousDisabled}
+        onPreviousClick={() => { this.handlePreviousPageClick() }}
+        currentPage={this.props.currentPage}
+        totalPages={Math.ceil(this.props.instances.length / this.props.instancesPerPage)}
+        loading={this.props.chunksLoading}
+        nextDisabled={isNextDisabled}
+        onNextClick={() => { this.handleNextPageClick() }}
+      />
     )
   }
 

+ 1 - 0
src/types/Config.js

@@ -25,4 +25,5 @@ export type Config = {
   providerSortPriority: { [providerName: string]: number },
   hiddenUsers: string[],
   passwordFields: string[],
+  mainListItemsPerPage: number,
 }