@ -0,0 +1,83 @@ | |||||
import api, { getLinks } from '../api' | |||||
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; | |||||
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; | |||||
export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL'; | |||||
export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST'; | |||||
export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS'; | |||||
export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL'; | |||||
export function fetchFavouritedStatuses() { | |||||
return (dispatch, getState) => { | |||||
dispatch(fetchFavouritedStatusesRequest()); | |||||
api(getState).get('/api/v1/favourites').then(response => { | |||||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||||
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); | |||||
}).catch(error => { | |||||
dispatch(fetchFavouritedStatusesFail(error)); | |||||
}); | |||||
}; | |||||
}; | |||||
export function fetchFavouritedStatusesRequest() { | |||||
return { | |||||
type: FAVOURITED_STATUSES_FETCH_REQUEST | |||||
}; | |||||
}; | |||||
export function fetchFavouritedStatusesSuccess(statuses, next) { | |||||
return { | |||||
type: FAVOURITED_STATUSES_FETCH_SUCCESS, | |||||
statuses, | |||||
next | |||||
}; | |||||
}; | |||||
export function fetchFavouritedStatusesFail(error) { | |||||
return { | |||||
type: FAVOURITED_STATUSES_FETCH_FAIL, | |||||
error | |||||
}; | |||||
}; | |||||
export function expandFavouritedStatuses() { | |||||
return (dispatch, getState) => { | |||||
const url = getState().getIn(['status_lists', 'favourites', 'next'], null); | |||||
if (url === null) { | |||||
return; | |||||
} | |||||
dispatch(expandFavouritedStatusesRequest()); | |||||
api(getState).get(url).then(response => { | |||||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||||
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); | |||||
}).catch(error => { | |||||
dispatch(expandFavouritedStatusesFail(error)); | |||||
}); | |||||
}; | |||||
}; | |||||
export function expandFavouritedStatusesRequest() { | |||||
return { | |||||
type: FAVOURITED_STATUSES_EXPAND_REQUEST | |||||
}; | |||||
}; | |||||
export function expandFavouritedStatusesSuccess(statuses, next) { | |||||
return { | |||||
type: FAVOURITED_STATUSES_EXPAND_SUCCESS, | |||||
statuses, | |||||
next | |||||
}; | |||||
}; | |||||
export function expandFavouritedStatusesFail(error) { | |||||
return { | |||||
type: FAVOURITED_STATUSES_EXPAND_FAIL, | |||||
error | |||||
}; | |||||
}; |
@ -0,0 +1,63 @@ | |||||
import { connect } from 'react-redux'; | |||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||||
import LoadingIndicator from '../../components/loading_indicator'; | |||||
import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites'; | |||||
import Column from '../ui/components/column'; | |||||
import StatusList from '../../components/status_list'; | |||||
import ColumnBackButton from '../public_timeline/components/column_back_button'; | |||||
import { defineMessages, injectIntl } from 'react-intl'; | |||||
const messages = defineMessages({ | |||||
heading: { id: 'column.favourites', defaultMessage: 'Favourites' } | |||||
}); | |||||
const mapStateToProps = state => ({ | |||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']), | |||||
loaded: state.getIn(['status_lists', 'favourites', 'loaded']), | |||||
me: state.getIn(['meta', 'me']) | |||||
}); | |||||
const Favourites = React.createClass({ | |||||
propTypes: { | |||||
params: React.PropTypes.object.isRequired, | |||||
dispatch: React.PropTypes.func.isRequired, | |||||
statusIds: ImmutablePropTypes.list.isRequired, | |||||
loaded: React.PropTypes.bool, | |||||
intl: React.PropTypes.object.isRequired, | |||||
me: React.PropTypes.number.isRequired | |||||
}, | |||||
mixins: [PureRenderMixin], | |||||
componentWillMount () { | |||||
this.props.dispatch(fetchFavouritedStatuses()); | |||||
}, | |||||
handleScrollToBottom () { | |||||
this.props.dispatch(expandFavouritedStatuses()); | |||||
}, | |||||
render () { | |||||
const { statusIds, loaded, intl, me } = this.props; | |||||
if (!loaded) { | |||||
return ( | |||||
<Column> | |||||
<LoadingIndicator /> | |||||
</Column> | |||||
); | |||||
} | |||||
return ( | |||||
<Column icon='star' heading={intl.formatMessage(messages.heading)}> | |||||
<ColumnBackButton /> | |||||
<StatusList statusIds={statusIds} me={me} onScrollToBottom={this.handleScrollToBottom} /> | |||||
</Column> | |||||
); | |||||
} | |||||
}); | |||||
export default connect(mapStateToProps)(injectIntl(Favourites)); |
@ -0,0 +1,25 @@ | |||||
import { showLoading, hideLoading } from 'react-redux-loading-bar'; | |||||
const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED']; | |||||
export default function loadingBarMiddleware(config = {}) { | |||||
const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes; | |||||
return ({ dispatch }) => next => (action) => { | |||||
if (action.type && !action.skipLoading) { | |||||
const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes; | |||||
const isPending = new RegExp(`${PENDING}$`, 'g'); | |||||
const isFulfilled = new RegExp(`${FULFILLED}$`, 'g'); | |||||
const isRejected = new RegExp(`${REJECTED}$`, 'g'); | |||||
if (action.type.match(isPending)) { | |||||
dispatch(showLoading()); | |||||
} else if (action.type.match(isFulfilled) || action.type.match(isRejected)) { | |||||
dispatch(hideLoading()); | |||||
} | |||||
} | |||||
return next(action); | |||||
}; | |||||
}; |
@ -0,0 +1,39 @@ | |||||
import { | |||||
FAVOURITED_STATUSES_FETCH_SUCCESS, | |||||
FAVOURITED_STATUSES_EXPAND_SUCCESS | |||||
} from '../actions/favourites'; | |||||
import Immutable from 'immutable'; | |||||
const initialState = Immutable.Map({ | |||||
favourites: Immutable.Map({ | |||||
next: null, | |||||
loaded: false, | |||||
items: Immutable.List() | |||||
}) | |||||
}); | |||||
const normalizeList = (state, listType, statuses, next) => { | |||||
return state.update(listType, listMap => listMap.withMutations(map => { | |||||
map.set('next', next); | |||||
map.set('loaded', true); | |||||
map.set('items', Immutable.List(statuses.map(item => item.id))); | |||||
})); | |||||
}; | |||||
const appendToList = (state, listType, statuses, next) => { | |||||
return state.update(listType, listMap => listMap.withMutations(map => { | |||||
map.set('next', next); | |||||
map.set('items', map.get('items').push(...statuses.map(item => item.id))); | |||||
})); | |||||
}; | |||||
export default function statusLists(state = initialState, action) { | |||||
switch(action.type) { | |||||
case FAVOURITED_STATUSES_FETCH_SUCCESS: | |||||
return normalizeList(state, 'favourites', action.statuses, action.next); | |||||
case FAVOURITED_STATUSES_EXPAND_SUCCESS: | |||||
return appendToList(state, 'favourites', action.statuses, action.next); | |||||
default: | |||||
return state; | |||||
} | |||||
}; |