From 5f9d95301cc8340a67e9bcc0a4e7d5ae00b5b4f9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Jun 2021 13:59:10 -0500 Subject: [PATCH] ScheduledStatuses: scaffolding --- app/soapbox/actions/scheduled_statuses.js | 87 +++++++++++++++++++ .../features/scheduled_statuses/index.js | 74 ++++++++++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + app/soapbox/reducers/scheduled_statuses.js | 45 ++++++++++ app/soapbox/reducers/status_lists.js | 18 ++++ 6 files changed, 230 insertions(+) create mode 100644 app/soapbox/actions/scheduled_statuses.js create mode 100644 app/soapbox/features/scheduled_statuses/index.js create mode 100644 app/soapbox/reducers/scheduled_statuses.js diff --git a/app/soapbox/actions/scheduled_statuses.js b/app/soapbox/actions/scheduled_statuses.js new file mode 100644 index 000000000..4e54846b8 --- /dev/null +++ b/app/soapbox/actions/scheduled_statuses.js @@ -0,0 +1,87 @@ +import api, { getLinks } from '../api'; + +export const SCHEDULED_STATUSES_FETCH_REQUEST = 'SCHEDULED_STATUSES_FETCH_REQUEST'; +export const SCHEDULED_STATUSES_FETCH_SUCCESS = 'SCHEDULED_STATUSES_FETCH_SUCCESS'; +export const SCHEDULED_STATUSES_FETCH_FAIL = 'SCHEDULED_STATUSES_FETCH_FAIL'; + +export const SCHEDULED_STATUSES_EXPAND_REQUEST = 'SCHEDULED_STATUSES_EXPAND_REQUEST'; +export const SCHEDULED_STATUSES_EXPAND_SUCCESS = 'SCHEDULED_STATUSES_EXPAND_SUCCESS'; +export const SCHEDULED_STATUSES_EXPAND_FAIL = 'SCHEDULED_STATUSES_EXPAND_FAIL'; + +export function fetchScheduledStatuses() { + return (dispatch, getState) => { + if (getState().getIn(['status_lists', 'scheduled_statuses', 'isLoading'])) { + return; + } + + dispatch(fetchScheduledStatusesRequest()); + + api(getState).get('/api/v1/scheduled_statuses').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchScheduledStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(fetchScheduledStatusesFail(error)); + }); + }; +}; + +export function fetchScheduledStatusesRequest() { + return { + type: SCHEDULED_STATUSES_FETCH_REQUEST, + }; +}; + +export function fetchScheduledStatusesSuccess(statuses, next) { + return { + type: SCHEDULED_STATUSES_FETCH_SUCCESS, + statuses, + next, + }; +}; + +export function fetchScheduledStatusesFail(error) { + return { + type: SCHEDULED_STATUSES_FETCH_FAIL, + error, + }; +}; + +export function expandScheduledStatuses() { + return (dispatch, getState) => { + const url = getState().getIn(['status_lists', 'scheduled_statuses', 'next'], null); + + if (url === null || getState().getIn(['status_lists', 'scheduled_statuses', 'isLoading'])) { + return; + } + + dispatch(expandScheduledStatusesRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandScheduledStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandScheduledStatusesFail(error)); + }); + }; +}; + +export function expandScheduledStatusesRequest() { + return { + type: SCHEDULED_STATUSES_EXPAND_REQUEST, + }; +}; + +export function expandScheduledStatusesSuccess(statuses, next) { + return { + type: SCHEDULED_STATUSES_EXPAND_SUCCESS, + statuses, + next, + }; +}; + +export function expandScheduledStatusesFail(error) { + return { + type: SCHEDULED_STATUSES_EXPAND_FAIL, + error, + }; +}; diff --git a/app/soapbox/features/scheduled_statuses/index.js b/app/soapbox/features/scheduled_statuses/index.js new file mode 100644 index 000000000..ace5684bd --- /dev/null +++ b/app/soapbox/features/scheduled_statuses/index.js @@ -0,0 +1,74 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Column from '../ui/components/column'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import StatusList from '../../components/status_list'; +import { fetchScheduledStatuses, expandScheduledStatuses } from '../../actions/scheduled_statuses'; +import { debounce } from 'lodash'; + +const messages = defineMessages({ + heading: { id: 'column.scheduled_statuses', defaultMessage: 'Scheduled Statuses' }, +}); + +const mapStateToProps = state => ({ + statusIds: state.getIn(['status_lists', 'scheduled_statuses', 'items']), + isLoading: state.getIn(['status_lists', 'scheduled_statuses', 'isLoading'], true), + hasMore: !!state.getIn(['status_lists', 'scheduled_statuses', 'next']), +}); + +export default @connect(mapStateToProps) +@injectIntl +class ScheduledStatuses extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, + statusIds: ImmutablePropTypes.list.isRequired, + intl: PropTypes.object.isRequired, + columnId: PropTypes.string, + multiColumn: PropTypes.bool, + hasMore: PropTypes.bool, + isLoading: PropTypes.bool, + }; + + componentDidMount() { + const { dispatch } = this.props; + dispatch(fetchScheduledStatuses()); + } + + handleLoadMore = debounce(() => { + this.props.dispatch(expandScheduledStatuses()); + }, 300, { leading: true }) + + + render() { + const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const pinned = !!columnId; + + const emptyMessage = ; + + return ( + + + + ); + } + +} diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 3dffbf238..cdd0652dc 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -100,6 +100,7 @@ import { Reports, ModerationLog, CryptoDonate, + ScheduledStatuses, } from './util/async-components'; // Dummy import, to make sure that ends up in the application bundle. @@ -300,6 +301,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index dc06d1677..c3c5dc9d7 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -233,3 +233,7 @@ export function ModerationLog() { export function CryptoDonate() { return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate'); } + +export function ScheduledStatuses() { + return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses'); +} diff --git a/app/soapbox/reducers/scheduled_statuses.js b/app/soapbox/reducers/scheduled_statuses.js new file mode 100644 index 000000000..9441406aa --- /dev/null +++ b/app/soapbox/reducers/scheduled_statuses.js @@ -0,0 +1,45 @@ +import { Map as ImmutableMap, fromJS } from 'immutable'; + +const importStatus = (state, status) => state.set(status.id, fromJS(status)); + +const importStatuses = (state, statuses) => + state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status))); + +const deleteStatus = (state, id, references) => { + references.forEach(ref => { + state = deleteStatus(state, ref[0], []); + }); + + return state.delete(id); +}; + +const initialState = ImmutableMap(); + +export default function statuses(state = initialState, action) { + switch(action.type) { + case STATUS_IMPORT: + return importStatus(state, action.status); + case STATUSES_IMPORT: + return importStatuses(state, action.statuses); + case STATUS_REVEAL: + return state.withMutations(map => { + action.ids.forEach(id => { + if (!(state.get(id) === undefined)) { + map.setIn([id, 'hidden'], false); + } + }); + }); + case STATUS_HIDE: + return state.withMutations(map => { + action.ids.forEach(id => { + if (!(state.get(id) === undefined)) { + map.setIn([id, 'hidden'], true); + } + }); + }); + case TIMELINE_DELETE: + return deleteStatus(state, action.id, action.references); + default: + return state; + } +}; diff --git a/app/soapbox/reducers/status_lists.js b/app/soapbox/reducers/status_lists.js index 9f8f28dee..3471eca34 100644 --- a/app/soapbox/reducers/status_lists.js +++ b/app/soapbox/reducers/status_lists.js @@ -26,6 +26,14 @@ import { PIN_SUCCESS, UNPIN_SUCCESS, } from '../actions/interactions'; +import { + SCHEDULED_STATUSES_FETCH_REQUEST, + SCHEDULED_STATUSES_FETCH_SUCCESS, + SCHEDULED_STATUSES_FETCH_FAIL, + SCHEDULED_STATUSES_EXPAND_REQUEST, + SCHEDULED_STATUSES_EXPAND_SUCCESS, + SCHEDULED_STATUSES_EXPAND_FAIL, +} from '../actions/scheduled_statuses'; const initialState = ImmutableMap({ favourites: ImmutableMap({ @@ -110,6 +118,16 @@ export default function statusLists(state = initialState, action) { return prependOneToList(state, 'pins', action.status); case UNPIN_SUCCESS: return removeOneFromList(state, 'pins', action.status); + case SCHEDULED_STATUSES_FETCH_REQUEST: + case SCHEDULED_STATUSES_EXPAND_REQUEST: + return state.setIn(['scheduled_statuses', 'isLoading'], true); + case SCHEDULED_STATUSES_FETCH_FAIL: + case SCHEDULED_STATUSES_EXPAND_FAIL: + return state.setIn(['scheduled_statuses', 'isLoading'], false); + case SCHEDULED_STATUSES_FETCH_SUCCESS: + return normalizeList(state, 'scheduled_statuses', action.statuses, action.next); + case SCHEDULED_STATUSES_EXPAND_SUCCESS: + return appendToList(state, 'scheduled_statuses', action.statuses, action.next); default: return state; }