Просмотр исходного кода

Show instances per page based on window height

Resizing and zooming affects the number of instances shown per page in
Wizard Instances selection page.

The minimum and maximum instances per page is configurable in
`config.js` at the `instancesPerPage:` line.
Sergiu Miclea 7 лет назад
Родитель
Сommit
ac71b2bceb

+ 1 - 1
src/components/atoms/Arrow/images/arrow-thick.js

@@ -3,7 +3,7 @@ export default color => `<?xml version="1.0" encoding="UTF-8"?>
     <!-- Generator: Sketch 52.3 (67297) - http://www.bohemiancoding.com/sketch -->
     <title>Rectangle Copy</title>
     <desc>Created with Sketch.</desc>
-    <g id="Symbols" transform="translateY(-1px)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+    <g id="Symbols" transform="translate(0 -1)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
         <g id="Icon/Chevron/Black" transform="translate(-5.000000, -1.000000)" stroke="${color}" stroke-width="1.5">
             <polyline id="Rectangle-Copy" transform="translate(6.000000, 7.500000) rotate(315.000000) translate(-6.000000, -7.500000) " points="9.8890873 3.6109127 9.8890873 11.3890873 2.1109127 11.3890873"></polyline>
         </g>

+ 1 - 1
src/components/atoms/Arrow/images/arrow.js

@@ -20,7 +20,7 @@ version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/
     <desc>Created with Sketch.</desc>
     <defs></defs>
     <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"
-     stroke-linecap="round" stroke-linejoin="round" transform="rotate(90deg)">
+     stroke-linecap="round" stroke-linejoin="round">
         <g id="202-Replica-Executions" transform="translate(-1197.000000, -194.000000)" stroke="${color}">
             <g id="Icon/Chevron/Grey"
             transform="translate(1200.000000, 200.000000) rotate(-90.000000)

+ 16 - 14
src/components/organisms/WizardInstances/test.jsx

@@ -30,15 +30,16 @@ let instances = [
   { id: 'i-2', flavor_name: 'Flavor name', instance_name: 'Instance name 2', num_cpu: 3, memory_mb: 1024 },
   { id: 'i-3', flavor_name: 'Flavor name', instance_name: 'Instance name 3', num_cpu: 3, memory_mb: 1024 },
 ]
+let onChunkSizeUpdate = () => { }
 
 describe('WizardInstances Component', () => {
   it('has correct number of instances', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     expect(wrapper.find('item-', true).length).toBe(instances.length)
   })
 
   it('has correct instances info', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     instances.forEach(instance => {
       expect(wrapper.find(`item-${instance.id}`).findText('itemName')).toBe(instance.instance_name)
       expect(wrapper.find(`item-${instance.id}`).findText('itemDetails')).toBe(`${instance.num_cpu} vCPU | ${instance.memory_mb} MB RAM | ${instance.flavor_name}`)
@@ -53,6 +54,7 @@ describe('WizardInstances Component', () => {
         { ...instances[0] },
         { ...instances[2] },
       ],
+      onChunkSizeUpdate,
     })
     expect(wrapper.findText('selInfo')).toBe('2 instances selected')
     expect(wrapper.find('item-i-1').prop('selected')).toBe(true)
@@ -61,56 +63,56 @@ describe('WizardInstances Component', () => {
   })
 
   it('renders current page', () => {
-    let wrapper = wrap({ instances, currentPage: 2, chunkSize: 2 })
+    let wrapper = wrap({ instances, currentPage: 2, chunkSize: 2, onChunkSizeUpdate })
     expect(wrapper.findText('currentPage')).toBe('2 of 2')
   })
 
   it('renders previous page disabled if page is 1', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     expect(wrapper.find('prevPageButton').prop('disabled')).toBe(true)
   })
 
   it('renders previous page enabled if page is greater than 1', () => {
-    let wrapper = wrap({ instances, currentPage: 3 })
+    let wrapper = wrap({ instances, currentPage: 3, onChunkSizeUpdate })
     expect(wrapper.find('prevPageButton').prop('disabled')).toBeFalsy()
     expect(wrapper.find('loadingStatus').length).toBe(0)
   })
 
   it('renders loading', () => {
-    let wrapper = wrap({ instances, currentPage: 1, loading: true })
+    let wrapper = wrap({ instances, currentPage: 1, loading: true, onChunkSizeUpdate })
     expect(wrapper.find('loadingStatus').length).toBe(1)
   })
 
   it('renders searching', () => {
-    let wrapper = wrap({ instances, currentPage: 1, searching: true })
+    let wrapper = wrap({ instances, currentPage: 1, searching: true, onChunkSizeUpdate })
     expect(wrapper.find('searchInput').prop('loading')).toBe(true)
   })
 
   it('renders search not found', () => {
-    let wrapper = wrap({ instances: [], currentPage: 1, searchNotFound: true })
+    let wrapper = wrap({ instances: [], currentPage: 1, searchNotFound: true, onChunkSizeUpdate })
     expect(wrapper.findText('notFoundText')).toBe('Your search returned no results')
     expect(wrapper.find('loadingChunks').length).toBe(0)
   })
 
   it('renders loading page', () => {
-    let wrapper = wrap({ instances, currentPage: 1, chunksLoading: true })
+    let wrapper = wrap({ instances, currentPage: 1, chunksLoading: true, onChunkSizeUpdate })
     expect(wrapper.find('loadingChunks').length).toBe(1)
   })
 
   it('enabled next page', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     expect(wrapper.find('nextPageButton').prop('disabled')).toBe(true)
-    wrapper = wrap({ instances, currentPage: 1, chunkSize: 2 })
+    wrapper = wrap({ instances, currentPage: 1, chunkSize: 2, onChunkSizeUpdate })
     expect(wrapper.find('nextPageButton').prop('disabled')).toBeFalsy()
   })
 
   it('dispatches next and previous page click, if enabled', () => {
     let onPageClick = sinon.spy()
-    let wrapper = wrap({ instances, currentPage: 1, onPageClick })
+    let wrapper = wrap({ instances, currentPage: 1, onPageClick, onChunkSizeUpdate })
     wrapper.find('nextPageButton').click()
     wrapper.find('prevPageButton').click()
     expect(onPageClick.callCount).toBe(0)
-    wrapper = wrap({ instances, currentPage: 2, onPageClick, chunkSize: 1 })
+    wrapper = wrap({ instances, currentPage: 2, onPageClick, chunkSize: 1, onChunkSizeUpdate })
     wrapper.find('nextPageButton').click()
     wrapper.find('prevPageButton').click()
     expect(onPageClick.callCount).toBe(2)
@@ -118,7 +120,7 @@ describe('WizardInstances Component', () => {
 
   it('dispaches reload click', () => {
     let onReloadClick = sinon.spy()
-    let wrapper = wrap({ instances, currentPage: 1, onReloadClick })
+    let wrapper = wrap({ instances, currentPage: 1, onReloadClick, onChunkSizeUpdate })
     wrapper.find('reloadButton').click()
     expect(onReloadClick.calledOnce).toBe(true)
   })

+ 24 - 3
src/components/pages/WizardPage/WizardPage.jsx

@@ -16,6 +16,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import styled from 'styled-components'
+import autobind from 'autobind-decorator'
 import { observer } from 'mobx-react'
 
 import WizardTemplate from '../../templates/WizardTemplate'
@@ -68,14 +69,23 @@ class WizardPage extends React.Component<Props, State> {
 
   contentRef: WizardPageContent
 
+  get instancesChunkSize() {
+    let { min, max } = wizardConfig.instancesPerPage
+    const instancesTableDiff = 505
+    const instancesItemHeight = 67
+    return Math.min(max, Math.max(min, Math.floor((window.innerHeight - instancesTableDiff) / instancesItemHeight)))
+  }
+
   componentWillMount() {
     this.initializeState()
+    this.handleResize()
   }
 
   componentDidMount() {
     document.title = 'Coriolis Wizard'
     KeyboardManager.onEnter('wizard', () => { this.handleEnterKey() })
     KeyboardManager.onEsc('wizard', () => { this.handleEscKey() })
+    window.addEventListener('resize', this.handleResize)
   }
 
   componentWillReceiveProps(newProps: Props) {
@@ -90,6 +100,12 @@ class WizardPage extends React.Component<Props, State> {
     wizardStore.clearData()
     instanceStore.cancelIntancesChunksLoading()
     KeyboardManager.removeKeyDown('wizard')
+    window.removeEventListener('resize', this.handleResize, false)
+  }
+
+  @autobind
+  handleResize() {
+    instanceStore.updateChunkSize(this.instancesChunkSize)
   }
 
   handleEnterKey() {
@@ -182,7 +198,7 @@ class WizardPage extends React.Component<Props, State> {
       endpointStore.getConnectionInfo(source).then(() => {
         if (source) {
           // Preload instances for 'vms' page
-          instanceStore.loadInstancesInChunks(source.id)
+          instanceStore.loadInstancesInChunks(source.id, this.instancesChunkSize)
         }
       }).catch(() => {
         this.handleSourceEndpointChange(null)
@@ -228,7 +244,7 @@ class WizardPage extends React.Component<Props, State> {
 
   handleInstancesReloadClick() {
     if (wizardStore.data.source) {
-      instanceStore.reloadInstances(wizardStore.data.source.id)
+      instanceStore.reloadInstances(wizardStore.data.source.id, this.instancesChunkSize)
     }
   }
 
@@ -242,6 +258,10 @@ class WizardPage extends React.Component<Props, State> {
     instanceStore.setPage(page)
   }
 
+  handleInstanceChunkSizeUpdate(chunkSize: number) {
+    instanceStore.updateChunkSize(chunkSize)
+  }
+
   handleOptionsChange(field: Field, value: any) {
     wizardStore.updateData({ networks: null })
     wizardStore.updateOptions({ field, value })
@@ -333,7 +353,7 @@ class WizardPage extends React.Component<Props, State> {
           // Check if user has permission for this endpoint
           endpointStore.getConnectionInfo(source).then(() => {
             // Preload instances for 'vms' page
-            instanceStore.loadInstancesInChunks(source.id)
+            instanceStore.loadInstancesInChunks(source.id, this.instancesChunkSize)
           }).catch(() => {
             this.handleSourceEndpointChange(null)
           })
@@ -477,6 +497,7 @@ class WizardPage extends React.Component<Props, State> {
             onInstancesReloadClick={() => { this.handleInstancesReloadClick() }}
             onInstanceClick={instance => { this.handleInstanceClick(instance) }}
             onInstancePageClick={page => { this.handleInstancePageClick(page) }}
+            onInstanceChunkSizeUpdate={chunkSize => { this.handleInstanceChunkSizeUpdate(chunkSize) }}
             onOptionsChange={(field, value) => { this.handleOptionsChange(field, value) }}
             onNetworkChange={(sourceNic, targetNetwork) => { this.handleNetworkChange(sourceNic, targetNetwork) }}
             onAddScheduleClick={schedule => { this.handleAddScheduleClick(schedule) }}

+ 1 - 0
src/config.js

@@ -85,6 +85,7 @@ export const wizardConfig = {
     { id: 'schedule', title: 'Schedule', breadcrumb: 'Schedule', excludeFrom: 'migration' },
     { id: 'summary', title: 'Summary', breadcrumb: 'Summary' },
   ],
+  instancesPerPage: { min: 3, max: Infinity },
 }
 
 // A list of providers for which `destination-options` API call(s) will be made in the Wizard

+ 16 - 7
src/stores/InstanceStore.js

@@ -115,7 +115,7 @@ class InstanceStore {
   lastEndpointId: string
   reqId: number
 
-  @action loadInstancesInChunks(endpointId: string, reload?: boolean) {
+  @action loadInstancesInChunks(endpointId: string, chunkSize?: number = 6, reload?: boolean) {
     ApiCaller.cancelRequests(`${endpointId}-chunk`)
 
     this.backgroundInstances = []
@@ -129,7 +129,7 @@ class InstanceStore {
 
     let loadNextChunk = (lastEndpointId?: string) => {
       let currentEndpointId = endpointId
-      InstanceSource.loadInstancesChunk(currentEndpointId, this.chunkSize, lastEndpointId, `${endpointId}-chunk`)
+      InstanceSource.loadInstancesChunk(currentEndpointId, chunkSize, lastEndpointId, `${endpointId}-chunk`)
         .then(instances => {
           if (currentEndpointId !== this.lastEndpointId) {
             return
@@ -141,7 +141,7 @@ class InstanceStore {
           }
           this.instancesLoading = false
 
-          if (instances.length < this.chunkSize) {
+          if (instances.length < chunkSize) {
             this.backgroundChunksLoading = false
             return
           }
@@ -199,11 +199,12 @@ class InstanceStore {
 
     this.searching = true
     this.searchChunksLoading = true
+    let chunkSize = this.chunkSize
 
     let loadNextChunk = (lastEndpointId?: string) => {
       InstanceSource.loadInstancesChunk(
         endpointId,
-        this.chunkSize,
+        chunkSize,
         lastEndpointId,
         `${endpointId}-chunk-search`,
         searchText
@@ -216,7 +217,7 @@ class InstanceStore {
         this.searchedInstances = [...this.searchedInstances, ...instances]
         this.searching = false
         this.searchNotFound = Boolean(this.searchedInstances.length === 0)
-        if (instances.length < this.chunkSize) {
+        if (instances.length < chunkSize) {
           this.searchChunksLoading = false
         }
         return loadNextChunk(instances[instances.length - 1].id)
@@ -225,22 +226,30 @@ class InstanceStore {
     loadNextChunk()
   }
 
-  @action reloadInstances(endpointId: string) {
+  @action reloadInstances(endpointId: string, chunkSize?: number) {
     this.searchNotFound = false
     this.searchText = ''
     this.currentPage = 1
-    this.loadInstancesInChunks(endpointId, true)
+    this.loadInstancesInChunks(endpointId, chunkSize, true)
   }
 
   @action cancelIntancesChunksLoading() {
     ApiCaller.cancelRequests(`${this.lastEndpointId}-chunk`)
     this.lastEndpointId = ''
+    this.searchNotFound = false
+    this.searchText = ''
+    this.currentPage = 1
   }
 
   @action setPage(page: number) {
     this.currentPage = page
   }
 
+  @action updateChunkSize(chunkSize: number) {
+    this.currentPage = 1
+    this.chunkSize = chunkSize
+  }
+
   @action loadInstancesDetails(endpointId: string, instancesInfo: Instance[], useLocalStorage?: boolean, quietError?: boolean): Promise<void> {
     // Use reqId to be able to uniquely identify the request so all but the latest request can be igonred and canceled
     this.reqId = !this.reqId ? 1 : this.reqId + 1