Skip to content

Commit

Permalink
Wip: make web backend
Browse files Browse the repository at this point in the history
  • Loading branch information
kmycode committed Sep 23, 2023
1 parent 0513db1 commit 5351751
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 3 deletions.
98 changes: 96 additions & 2 deletions app/javascript/mastodon/actions/circles.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import api from '../api';
import api, { getLinks } from '../api';

import { showAlertForError } from './alerts';
import { importFetchedAccounts } from './importer';
import { importFetchedAccounts, importFetchedStatuses } from './importer';

export const CIRCLE_FETCH_REQUEST = 'CIRCLE_FETCH_REQUEST';
export const CIRCLE_FETCH_SUCCESS = 'CIRCLE_FETCH_SUCCESS';
Expand Down Expand Up @@ -50,6 +50,14 @@ export const CIRCLE_ADDER_CIRCLES_FETCH_REQUEST = 'CIRCLE_ADDER_CIRCLES_FETCH_RE
export const CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS = 'CIRCLE_ADDER_CIRCLES_FETCH_SUCCESS';
export const CIRCLE_ADDER_CIRCLES_FETCH_FAIL = 'CIRCLE_ADDER_CIRCLES_FETCH_FAIL';

export const CIRCLE_STATUSES_FETCH_REQUEST = 'CIRCLE_STATUSES_FETCH_REQUEST';
export const CIRCLE_STATUSES_FETCH_SUCCESS = 'CIRCLE_STATUSES_FETCH_SUCCESS';
export const CIRCLE_STATUSES_FETCH_FAIL = 'CIRCLE_STATUSES_FETCH_FAIL';

export const CIRCLE_STATUSES_EXPAND_REQUEST = 'CIRCLE_STATUSES_EXPAND_REQUEST';
export const CIRCLE_STATUSES_EXPAND_SUCCESS = 'CIRCLE_STATUSES_EXPAND_SUCCESS';
export const CIRCLE_STATUSES_EXPAND_FAIL = 'CIRCLE_STATUSES_EXPAND_FAIL';

export const fetchCircle = id => (dispatch, getState) => {
if (getState().getIn(['circles', id])) {
return;
Expand Down Expand Up @@ -370,3 +378,89 @@ export const removeFromCircleAdder = circleId => (dispatch, getState) => {
dispatch(removeFromCircle(circleId, getState().getIn(['circleAdder', 'accountId'])));
};

export function fetchCircleStatuses(circleId) {
return (dispatch, getState) => {
if (getState().getIn(['circles', circleId, 'statuses', 'isLoading'])) {
return;
}

dispatch(fetchCircleStatusesRequest(circleId));

api(getState).get(`/api/v1/circles/${circleId}/statuses`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchCircleStatusesSuccess(circleId, response.data, next ? next.uri : null));
}).catch(error => {
dispatch(fetchCircleStatusesFail(circleId, error));
});
};
}

export function fetchCircleStatusesRequest(id) {
return {
type: CIRCLE_STATUSES_FETCH_REQUEST,
id,
};
}

export function fetchCircleStatusesSuccess(id, statuses, next) {
return {
type: CIRCLE_STATUSES_FETCH_SUCCESS,
id,
statuses,
next,
};
}

export function fetchCircleStatusesFail(id, error) {
return {
type: CIRCLE_STATUSES_FETCH_FAIL,
id,
error,
};
}

export function expandCircleStatuses(circleId) {
return (dispatch, getState) => {
const url = getState().getIn(['circles', circleId, 'statuses', 'next'], null);

if (url === null || getState().getIn(['circles', circleId, 'statuses', 'isLoading'])) {
return;
}

dispatch(expandCircleStatusesRequest(circleId));

api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandCircleStatusesSuccess(circleId, response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandCircleStatusesFail(circleId, error));
});
};
}

export function expandCircleStatusesRequest(id) {
return {
type: CIRCLE_STATUSES_EXPAND_REQUEST,
id,
};
}

export function expandCircleStatusesSuccess(id, statuses, next) {
return {
type: CIRCLE_STATUSES_EXPAND_SUCCESS,
id,
statuses,
next,
};
}

export function expandCircleStatusesFail(id, error) {
return {
type: CIRCLE_STATUSES_EXPAND_FAIL,
id,
error,
};
}

179 changes: 179 additions & 0 deletions app/javascript/mastodon/features/circle_statuses/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import PropTypes from 'prop-types';

import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';

import { Helmet } from 'react-helmet';

import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';

import { debounce } from 'lodash';

import { deleteCircle, expandCircleStatuses, fetchCircle, fetchCircleStatuses , setupCircleEditor } from 'mastodon/actions/circles';
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import { openModal } from 'mastodon/actions/modal';
import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import StatusList from 'mastodon/components/status_list';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import Column from 'mastodon/features/ui/components/column';
import { getCircleStatusList } from 'mastodon/selectors';


const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' },
deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' },
heading: { id: 'column.circles', defaultMessage: 'Circles' },
});

const mapStateToProps = (state, { params }) => ({
circle: state.getIn(['circles', params.id]),
statusIds: getCircleStatusList(state, params.id),
isLoading: state.getIn(['circles', params.id, 'isLoading'], true),
isEditing: state.getIn(['circleEditor', 'circleId']) === params.id,
hasMore: !!state.getIn(['circles', params.id, 'next']),
});

class CircleStatuses extends ImmutablePureComponent {

static contextTypes = {
router: PropTypes.object,
};

static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
circle: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
};

UNSAFE_componentWillMount () {
this.props.dispatch(fetchCircle(this.props.params.id));
this.props.dispatch(fetchCircleStatuses(this.props.params.id));
}

handlePin = () => {
const { columnId, dispatch } = this.props;

if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('CIRCLE_STATUSES', { id: this.props.params.id }));
this.context.router.history.push('/');
}
};

handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
};

handleHeaderClick = () => {
this.column.scrollTop();
};

handleEditClick = () => {
this.props.dispatch(setupCircleEditor(this.props.params.id));
};

handleDeleteClick = () => {
const { dispatch, columnId, intl } = this.props;
const { id } = this.props.params;

dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: intl.formatMessage(messages.deleteMessage),
confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => {
dispatch(deleteCircle(id));

if (columnId) {
dispatch(removeColumn(columnId));
} else {
this.context.router.history.push('/circles');
}
},
},
}));
};

setRef = c => {
this.column = c;
};

handleLoadMore = debounce(() => {
this.props.dispatch(expandCircleStatuses());
}, 300, { leading: true });

render () {
const { intl, circle, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const pinned = !!columnId;

if (typeof circle === 'undefined') {
return (
<Column>
<div className='scrollable'>
<LoadingIndicator />
</div>
</Column>
);
} else if (circle === false) {
return (
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
);
}

const emptyMessage = <FormattedMessage id='empty_column.circle_statuses' defaultMessage="You don't have any circle posts yet. When you post one as circle, it will show up here." />;

return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
<ColumnHeader
icon='user-circle'
title={circle.get('title')}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
<div className='column-settings__row column-header__links'>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
<Icon id='pencil' /> <FormattedMessage id='circles.edit' defaultMessage='Edit circle' />
</button>

<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
<Icon id='trash' /> <FormattedMessage id='circles.delete' defaultMessage='Delete circle' />
</button>
</div>
</ColumnHeader>

<StatusList
trackScroll={!pinned}
statusIds={statusIds}
scrollKey={`circle_statuses-${columnId}`}
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
/>

<Helmet>
<title>{intl.formatMessage(messages.heading)}</title>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}

}

export default connect(mapStateToProps)(injectIntl(CircleStatuses));
43 changes: 42 additions & 1 deletion app/javascript/mastodon/reducers/circles.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { List as ImmutableList, fromJS } from 'immutable';
import { List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';

import {
CIRCLE_FETCH_SUCCESS,
Expand All @@ -7,6 +7,12 @@ import {
CIRCLE_CREATE_SUCCESS,
CIRCLE_UPDATE_SUCCESS,
CIRCLE_DELETE_SUCCESS,
CIRCLE_STATUSES_FETCH_REQUEST,
CIRCLE_STATUSES_FETCH_SUCCESS,
CIRCLE_STATUSES_FETCH_FAIL,
CIRCLE_STATUSES_EXPAND_REQUEST,
CIRCLE_STATUSES_EXPAND_SUCCESS,
CIRCLE_STATUSES_EXPAND_FAIL,
} from '../actions/circles';

const initialState = ImmutableList();
Expand All @@ -21,6 +27,31 @@ const normalizeLists = (state, circles) => {
return state;
};

const normalizeCircleStatuses = (state, circleId, statuses, next) => {
return state.updateIn([circleId, 'statuses'], listMap => listMap.withMutations(map => {
map.set('next', next);
map.set('loaded', true);
map.set('isLoading', false);
map.set('items', ImmutableOrderedSet(statuses.map(item => item.id)));
}));
};

const appendToCircleStatuses = (state, circleId, statuses, next) => {
return appendToCircleStatusesById(state, circleId, statuses.map(item => item.id), next);
};

const appendToCircleStatusesById = (state, circleId, statuses, next) => {
return state.updateIn([circleId, 'statuses'], listMap => listMap.withMutations(map => {
if (typeof next !== 'undefined') {
map.set('next', next);
}
map.set('isLoading', false);
if (map.get('items')) {
map.set('items', map.get('items').union(statuses));
}
}));
};

export default function circles(state = initialState, action) {
switch(action.type) {
case CIRCLE_FETCH_SUCCESS:
Expand All @@ -32,6 +63,16 @@ export default function circles(state = initialState, action) {
case CIRCLE_DELETE_SUCCESS:
case CIRCLE_FETCH_FAIL:
return state.set(action.id, false);
case CIRCLE_STATUSES_FETCH_REQUEST:
case CIRCLE_STATUSES_EXPAND_REQUEST:
return state.setIn([action.id, 'statuses', 'isLoading'], true);
case CIRCLE_STATUSES_FETCH_FAIL:
case CIRCLE_STATUSES_EXPAND_FAIL:
return state.setIn([action.id, 'statuses', 'isLoading'], false);
case CIRCLE_STATUSES_FETCH_SUCCESS:
return normalizeCircleStatuses(state, action.id, action.statuses, action.next);
case CIRCLE_STATUSES_EXPAND_SUCCESS:
return appendToCircleStatuses(state, action.id, action.statuses, action.next);
default:
return state;
}
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/mastodon/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,7 @@ export const getStatusList = createSelector([
export const getBookmarkCategoryStatusList = createSelector([
(state, bookmarkCategoryId) => state.getIn(['bookmark_categories', bookmarkCategoryId, 'items']),
], (items) => items ? items.toList() : ImmutableList());

export const getCircleStatusList = createSelector([
(state, circleId) => state.getIn(['circles', circleId, 'statuses', 'items']),
], (items) => items ? items.toList() : ImmutableList());

0 comments on commit 5351751

Please sign in to comment.