Skip to content

Commit

Permalink
Virtual list for Plannings
Browse files Browse the repository at this point in the history
  • Loading branch information
vied12 committed May 17, 2017
1 parent bffc72c commit 1e174a4
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 91 deletions.
6 changes: 3 additions & 3 deletions client/actions/planning.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,11 @@ const receivePlannings = (plannings) => ({
* @param {object} query - Query object used when requesting the planning items
* @return thunk function
*/
const performFetchRequest = (query={}) => (
const performFetchRequest = ({ source, where }) => (
(dispatch, getState, { api }) => (
api('planning').query({
source: query.source,
where: query.where,
source: source,
where: where,
embedded: { original_creator: 1 }, // nest creator to planning
max_results: 10000,
timestamp: new Date(),
Expand Down
1 change: 1 addition & 0 deletions client/components/EventsList/_test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const events = {
}

describe('<EventsList />', () => {
// Give the space to Autosizer to display the list
beforeEach(() => (
spyOn(AutoSizer.prototype, 'render').and.callFake(function render() {
return (
Expand Down
5 changes: 0 additions & 5 deletions client/containers/EventsListContainer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ class EventsListComponent extends React.Component {
super(props)
}

componentWillMount() {
// load events for the first time
this.props.loadEvents(this.props.currentSearch && this.props.currentSearch.fulltext)
}

toggleAdvancedSearch() {
if (this.props.advancedSearchOpened) {
this.props.closeAdvancedSearch()
Expand Down
98 changes: 98 additions & 0 deletions client/containers/PlanningList/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'
import { PlanningItem } from '../../components/index'
import * as selectors from '../../selectors'
import * as actions from '../../actions'
import { List, AutoSizer } from 'react-virtualized'
import { connect } from 'react-redux'
import { LIST_ITEM_HEIGHT, PLANNING_LIST_ITEM_MARGIN_HEIGHT } from '../../constants'

class PlanningList extends React.Component {

rowRenderer({ index, key, style }) {
const {
plannings,
currentPlanning,
planningsEvents,
openPlanningEditor,
handlePlanningUnspike,
handlePlanningSpike,
privileges,
} = this.props
const planning = plannings[index]
return (
<div key={key} style={style}>
<PlanningItem
key={key}
style={style}
active={currentPlanning && currentPlanning._id === planning._id}
item={planning}
event={planningsEvents[planning._id]}
onSpike={handlePlanningSpike}
onUnspike={handlePlanningUnspike}
onClick={openPlanningEditor.bind(null, planning._id)}
privileges={privileges} />
</div>
)
}

render() {
const { plannings } = this.props
return (
<div className="PlanningList">
<AutoSizer>
{({ height, width }) => (
<List
rowRenderer={this.rowRenderer.bind(this)}
height={height}
width={width}
plannings={plannings}
rowCount={plannings.length}
rowHeight={LIST_ITEM_HEIGHT + PLANNING_LIST_ITEM_MARGIN_HEIGHT}
/>
)}
</AutoSizer>
</div>
)
}
}

PlanningList.propTypes = {
plannings: React.PropTypes.array.isRequired,
currentPlanning: React.PropTypes.object,
planningsEvents: React.PropTypes.object,
openPlanningEditor: React.PropTypes.func.isRequired,
handlePlanningSpike: React.PropTypes.func.isRequired,
handlePlanningUnspike: React.PropTypes.func.isRequired,
privileges: React.PropTypes.object.isRequired,
}

const mapStateToProps = (state) => ({
currentPlanning: selectors.getCurrentPlanning(state),
plannings: selectors.getCurrentAgendaPlannings(state),
planningsEvents: selectors.getCurrentAgendaPlanningsEvents(state),
privileges: selectors.getPrivileges(state),
})

const mapDispatchToProps = (dispatch) => ({
openPlanningEditor: (planning) => (dispatch(actions.openPlanningEditor(planning))),
handlePlanningSpike: (planning) => {
dispatch(actions.showModal({
modalType: 'CONFIRMATION',
modalProps: {
body: `Are you sure you want to spike the planning item ${planning.slugline} ?`,
action: () => dispatch(actions.spikePlanning(planning)),
},
}))
},
handlePlanningUnspike: (planning) => {
dispatch(actions.showModal({
modalType: 'CONFIRMATION',
modalProps: {
body: `Are you sure you want to unspike the planning item ${planning.slugline} ?`,
action: () => dispatch(actions.unspikePlanning(planning)),
},
}))
},
})

export default connect(mapStateToProps, mapDispatchToProps)(PlanningList)
20 changes: 17 additions & 3 deletions client/containers/PlanningPanelContainer/_test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,22 @@ import { mount } from 'enzyme'
import React from 'react'
import { PlanningItem } from '../../components'
import { PlanningPanelContainer } from '../index'
import { AutoSizer } from 'react-virtualized'

describe('planning', () => {
// Give the space to Autosizer to display the list
beforeEach(() => (
spyOn(AutoSizer.prototype, 'render').and.callFake(function render() {
return (
<div ref={this._setRef}>
{this.props.children({
width: 200,
height: 400,
})}
</div>
)
})
))
describe('containers', () => {
describe('<PlanningPanelContainer />', () => {
const initialState = {
Expand Down Expand Up @@ -184,7 +198,7 @@ describe('planning', () => {
</Provider>
)

wrapper.find('PlanningPanel').props().handlePlanningSpike(item)
wrapper.find('PlanningList').props().handlePlanningSpike(item)
expect(store.getState().modal.modalType).toBe('CONFIRMATION')
expect(store.getState().modal.modalProps.body).toBe(
'Are you sure you want to spike the planning item Plan1 ?'
Expand All @@ -203,12 +217,12 @@ describe('planning', () => {
</Provider>
)

wrapper.find('PlanningPanel').props().handlePlanningUnspike(item)
wrapper.find('PlanningList').props().handlePlanningUnspike(item)
expect(store.getState().modal.modalType).toBe('CONFIRMATION')
expect(store.getState().modal.modalProps.body).toBe(
'Are you sure you want to unspike the planning item Plan1 ?'
)
})
})
})
})
})
58 changes: 5 additions & 53 deletions client/containers/PlanningPanelContainer/index.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import React from 'react'
import { connect } from 'react-redux'
import * as actions from '../../actions'
import { SelectAgenda, EditPlanningPanelContainer } from '../index'
import { PlanningItem, QuickAddPlanning, Toggle, SearchBar } from '../../components'
import { SelectAgenda, EditPlanningPanelContainer, PlanningList } from '../index'
import { QuickAddPlanning, Toggle, SearchBar } from '../../components'
import * as selectors from '../../selectors'
import './style.scss'

class PlanningPanel extends React.Component {

componentDidMount() {
this.props.fetchPlannings()
}

handleDragEnter(e) {
e.dataTransfer.dropEffect = 'copy'
}
Expand All @@ -27,13 +23,8 @@ class PlanningPanel extends React.Component {
render() {
const {
planningList,
openPlanningEditor,
currentAgenda,
handlePlanningSpike,
handlePlanningUnspike,
onPlanningCreation,
planningsEvents,
currentPlanning,
planningsAreLoading,
editPlanningViewOpen,
isEventListShown,
Expand Down Expand Up @@ -95,17 +86,9 @@ class PlanningPanel extends React.Component {
{currentAgenda && privileges.planning_planning_management === 1 && currentAgenda.state !== 'spiked' &&
<QuickAddPlanning className="ListItem" onPlanningCreation={onPlanningCreation}/>
}
{(planningList && planningList.length > 0) && planningList.map((planning) => (
<PlanningItem
key={planning._id}
active={currentPlanning && currentPlanning._id === planning._id}
item={planning}
event={planningsEvents[planning._id]}
onSpike={handlePlanningSpike}
onUnspike={handlePlanningUnspike}
onClick={openPlanningEditor.bind(null, planning._id)}
privileges={privileges} />
))}
{(planningList && planningList.length > 0) &&
<PlanningList />
}
</ul>
{
planningsAreLoading &&
Expand All @@ -132,14 +115,8 @@ class PlanningPanel extends React.Component {

PlanningPanel.propTypes = {
currentAgenda: React.PropTypes.object,
currentPlanning: React.PropTypes.object,
planningsEvents: React.PropTypes.object,
fetchPlannings: React.PropTypes.func.isRequired,
planningList: React.PropTypes.array.isRequired,
planningsAreLoading: React.PropTypes.bool,
openPlanningEditor: React.PropTypes.func.isRequired,
handlePlanningSpike: React.PropTypes.func,
handlePlanningUnspike: React.PropTypes.func,
onPlanningCreation: React.PropTypes.func,
editPlanningViewOpen: React.PropTypes.bool,
addEventToCurrentAgenda: React.PropTypes.func,
Expand All @@ -156,40 +133,16 @@ PlanningPanel.propTypes = {

const mapStateToProps = (state) => ({
currentAgenda: selectors.getCurrentAgenda(state),
currentPlanning: selectors.getCurrentPlanning(state),
planningList: selectors.getCurrentAgendaPlannings(state),
planningsAreLoading: state.agenda.agendasAreLoading || state.planning.planningsAreLoading,
editPlanningViewOpen: state.planning.editorOpened,
planningsEvents: selectors.getCurrentAgendaPlanningsEvents(state),
isEventListShown: selectors.isEventListShown(state),
onlyFuture: state.planning.onlyFuture,
onlySpiked: state.planning.onlySpiked,
privileges: selectors.getPrivileges(state),
})

const mapDispatchToProps = (dispatch) => ({
handlePlanningSpike: (planning) => {
dispatch(actions.showModal({
modalType: 'CONFIRMATION',
modalProps: {
body: `Are you sure you want to spike the planning item ${planning.slugline} ?`,
action: () => dispatch(actions.spikePlanning(planning)),
},
}))
},
handlePlanningUnspike: (planning) => {
dispatch(actions.showModal({
modalType: 'CONFIRMATION',
modalProps: {
body: `Are you sure you want to unspike the planning item ${planning.slugline} ?`,
action: () => dispatch(actions.unspikePlanning(planning)),
},
}))
},
fetchPlannings: () => {
dispatch(actions.fetchAgendas())
dispatch(actions.fetchPlannings())
},
onPlanningCreation: (planning) => (
// save planning and open the plannning editor
dispatch(actions.savePlanningAndReloadCurrentAgenda(planning))
Expand All @@ -198,7 +151,6 @@ const mapDispatchToProps = (dispatch) => ({
))
),
handleSearch: (text) => (dispatch(actions.planningFilterByKeyword(text))),
openPlanningEditor: (planning) => (dispatch(actions.openPlanningEditor(planning))),
addEventToCurrentAgenda: (event) => (dispatch(actions.addEventToCurrentAgenda(event))),
toggleEventsList: () => (dispatch(actions.toggleEventsList())),
onManageAgendasClick: () => (dispatch(actions.showModal({ modalType: 'MANAGE_AGENDAS' }))),
Expand Down
7 changes: 6 additions & 1 deletion client/containers/PlanningPanelContainer/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,19 @@
&__list {
flex-grow: 1;
height: 100%;
.list-view {
> .list-view {
overflow: auto;
box-shadow: inherit;
position: absolute;
top: $nav-height;
right: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
.PlanningList {
flex: 1 1 auto;
}
}
}
.ListItem {
Expand Down
1 change: 1 addition & 0 deletions client/containers/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { AddGeoLookupInput } from './AddGeoLookupInput/index'
export { EventsListContainer } from './EventsListContainer/index'
export { default as PlanningList } from './PlanningList/index'
export { PlanningPanelContainer } from './PlanningPanelContainer/index'
export { PlanningApp } from './PlanningApp'
export { EventForm } from './EventForm/index'
Expand Down
37 changes: 12 additions & 25 deletions client/controllers/PlanningController.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,7 @@ export function PlanningController(
) {
// create the application store
const store = createStore({
initialState: {
events: {
events: {},
eventsInList: [],
show: true,
search: {
currentSearch: $location.search().searchEvent &&
JSON.parse($location.search().searchEvent),
advancedSearchOpened: false,
},
},
planning: {
editorOpened: false,
currentPlanningId: null,
planningsAreLoading: false,
onlyFuture: true,
plannings: {}, // plannings stored by _id
},
agenda: {
agendas: [],
agendasAreLoading: false,
currentAgendaId: $location.search().agenda,
},
config: config,
},
initialState: { config: config },
extraArguments: {
api,
$location,
Expand All @@ -74,6 +50,17 @@ export function PlanningController(
store.dispatch(actions.loadCVocabularies())
store.dispatch(actions.loadIngestProviders())
store.dispatch(actions.loadPrivileges())
store.dispatch(actions.fetchEvents({
fulltext: JSON.parse(
$location.search().searchEvent || '{}'
).fulltext,
}))
store.dispatch(actions.fetchAgendas())
.then(() => {
if ($location.search().agenda) {
return store.dispatch(actions.selectAgenda($location.search().agenda))
}
})
// render the planning application
ReactDOM.render(
<Provider store={store}>
Expand Down
2 changes: 1 addition & 1 deletion client/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const getAgendas = (state) => state.agenda.agendas
export const getCurrentPlanningId = (state) => state.planning.currentPlanningId
export const getEvents = (state) => state.events.events
export const isEventListShown = (state) =>state.events.show
export const getPreviousEventRequestParams = (state) => state.events.lastRequestParams
export const getPreviousEventRequestParams = (state) => get(state.events, 'lastRequestParams', {})
export const getCurrentAgendaId = (state) => state.agenda.currentAgendaId
export const getStoredPlannings = (state) => state.planning.plannings
export const isOnlyFutureFiltered = (state) => state.planning.onlyFuture
Expand Down

0 comments on commit 1e174a4

Please sign in to comment.