From 50797119ff205e5f535a70ab40f00b3149a07049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 13 Apr 2022 16:07:50 +0200 Subject: [PATCH] typescript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/list_timeline/index.js | 158 ------------------ app/soapbox/features/list_timeline/index.tsx | 125 ++++++++++++++ .../lists/components/new_list_form.js | 82 --------- .../lists/components/new_list_form.tsx | 59 +++++++ app/soapbox/features/lists/index.js | 89 ---------- app/soapbox/features/lists/index.tsx | 77 +++++++++ app/soapbox/normalizers/index.ts | 1 + app/soapbox/normalizers/list.ts | 19 +++ .../reducers/{list_adder.js => list_adder.ts} | 33 ++-- .../{list_editor.js => list_editor.ts} | 43 +++-- app/soapbox/reducers/lists.js | 38 ----- app/soapbox/reducers/lists.ts | 46 +++++ 12 files changed, 371 insertions(+), 399 deletions(-) delete mode 100644 app/soapbox/features/list_timeline/index.js create mode 100644 app/soapbox/features/list_timeline/index.tsx delete mode 100644 app/soapbox/features/lists/components/new_list_form.js create mode 100644 app/soapbox/features/lists/components/new_list_form.tsx delete mode 100644 app/soapbox/features/lists/index.js create mode 100644 app/soapbox/features/lists/index.tsx create mode 100644 app/soapbox/normalizers/list.ts rename app/soapbox/reducers/{list_adder.js => list_adder.ts} (51%) rename app/soapbox/reducers/{list_editor.js => list_editor.ts} (68%) delete mode 100644 app/soapbox/reducers/lists.js create mode 100644 app/soapbox/reducers/lists.ts diff --git a/app/soapbox/features/list_timeline/index.js b/app/soapbox/features/list_timeline/index.js deleted file mode 100644 index 7770aa3f2..000000000 --- a/app/soapbox/features/list_timeline/index.js +++ /dev/null @@ -1,158 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; - -import { Button, Spinner } from 'soapbox/components/ui'; -import Column from 'soapbox/features/ui/components/column'; - -import { fetchList, deleteList } from '../../actions/lists'; -import { openModal } from '../../actions/modals'; -import { connectListStream } from '../../actions/streaming'; -import { expandListTimeline } from '../../actions/timelines'; -import MissingIndicator from '../../components/missing_indicator'; -import StatusListContainer from '../ui/containers/status_list_container'; - -const messages = defineMessages({ - deleteHeading: { id: 'confirmations.delete_list.heading', defaultMessage: 'Delete list' }, - deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, - deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' }, -}); - -const mapStateToProps = (state, props) => ({ - list: state.getIn(['lists', props.params.id]), - // hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0, -}); - -export default @connect(mapStateToProps) -@injectIntl -@withRouter -class ListTimeline extends React.PureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - // hasUnread: PropTypes.bool, - list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), - intl: PropTypes.object.isRequired, - history: PropTypes.object, - }; - - componentDidMount() { - this.handleConnect(this.props.params.id); - } - - componentWillUnmount() { - this.handleDisconnect(); - } - - componentDidUpdate(prevProps) { - if (this.props.params.id !== prevProps.params.id) { - this.handleDisconnect(); - this.handleConnect(this.props.params.id); - } - } - - handleConnect(id) { - const { dispatch } = this.props; - - dispatch(fetchList(id)); - dispatch(expandListTimeline(id)); - - this.disconnect = dispatch(connectListStream(id)); - } - - handleDisconnect() { - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } - - handleLoadMore = maxId => { - const { id } = this.props.params; - this.props.dispatch(expandListTimeline(id, { maxId })); - } - - handleEditClick = () => { - this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id })); - } - - handleDeleteClick = () => { - const { dispatch, intl } = this.props; - const { id } = this.props.params; - - dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/icons/trash.svg'), - heading: intl.formatMessage(messages.deleteHeading), - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteList(id)); - this.props.history.push('/lists'); - }, - })); - } - - render() { - const { list } = this.props; - const { id } = this.props.params; - const title = list ? list.get('title') : id; - - if (typeof list === 'undefined') { - return ( - -
- -
-
- ); - } else if (list === false) { - return ( - - ); - } - - const emptyMessage = ( -
- -

- -
- ); - - return ( - - {/* -
- - - - -
- - - - - -
-
*/} - - -
- ); - } - -} diff --git a/app/soapbox/features/list_timeline/index.tsx b/app/soapbox/features/list_timeline/index.tsx new file mode 100644 index 000000000..2697c201c --- /dev/null +++ b/app/soapbox/features/list_timeline/index.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { useEffect } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { useDispatch } from 'react-redux'; +import { useParams } from 'react-router-dom'; + +import { fetchList } from 'soapbox/actions/lists'; +import { openModal } from 'soapbox/actions/modals'; +import { connectListStream } from 'soapbox/actions/streaming'; +import { expandListTimeline } from 'soapbox/actions/timelines'; +import MissingIndicator from 'soapbox/components/missing_indicator'; +import { Button, Spinner } from 'soapbox/components/ui'; +import Column from 'soapbox/features/ui/components/column'; +import { useAppSelector } from 'soapbox/hooks'; + +import StatusListContainer from '../ui/containers/status_list_container'; + +// const messages = defineMessages({ +// deleteHeading: { id: 'confirmations.delete_list.heading', defaultMessage: 'Delete list' }, +// deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, +// deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' }, +// }); + +const ListTimeline: React.FC = () => { + const dispatch = useDispatch(); + const { id } = useParams<{ id: string }>(); + // const intl = useIntl(); + // const history = useHistory(); + + const list = useAppSelector((state) => state.lists.get(id)); + // const hasUnread = useAppSelector((state) => state.timelines.getIn([`list:${props.params.id}`, 'unread']) > 0); + + useEffect(() => { + const disconnect = handleConnect(id); + + return () => { + disconnect(); + }; + }, [id]); + + const handleConnect = (id: string) => { + dispatch(fetchList(id)); + dispatch(expandListTimeline(id)); + + return dispatch(connectListStream(id)); + }; + + const handleLoadMore = (maxId: string) => { + dispatch(expandListTimeline(id, { maxId })); + }; + + const handleEditClick = () => { + dispatch(openModal('LIST_EDITOR', { listId: id })); + }; + + // const handleDeleteClick = () => { + // dispatch(openModal('CONFIRM', { + // icon: require('@tabler/icons/icons/trash.svg'), + // heading: intl.formatMessage(messages.deleteHeading), + // message: intl.formatMessage(messages.deleteMessage), + // confirm: intl.formatMessage(messages.deleteConfirm), + // onConfirm: () => { + // dispatch(deleteList(id)); + // history.push('/lists'); + // }, + // })); + // }; + + const title = list ? list.get('title') : id; + + if (typeof list === 'undefined') { + return ( + +
+ +
+
+ ); + } else if (list === false) { + return ( + + ); + } + + const emptyMessage = ( +
+ +

+ +
+ ); + + return ( + + {/* +
+ + + + +
+ + + + + +
+
*/} + + +
+ ); +}; + +export default ListTimeline; diff --git a/app/soapbox/features/lists/components/new_list_form.js b/app/soapbox/features/lists/components/new_list_form.js deleted file mode 100644 index a065f0b70..000000000 --- a/app/soapbox/features/lists/components/new_list_form.js +++ /dev/null @@ -1,82 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; - -import { changeListEditorTitle, submitListEditor } from '../../../actions/lists'; -import { Button } from '../../../components/ui'; - -const messages = defineMessages({ - label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' }, - title: { id: 'lists.new.create', defaultMessage: 'Add list' }, - create: { id: 'lists.new.create_title', defaultMessage: 'Create' }, -}); - -const mapStateToProps = state => ({ - value: state.getIn(['listEditor', 'title']), - disabled: state.getIn(['listEditor', 'isSubmitting']), -}); - -const mapDispatchToProps = dispatch => ({ - onChange: value => dispatch(changeListEditorTitle(value)), - onSubmit: () => dispatch(submitListEditor(true)), -}); - -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl -class NewListForm extends React.PureComponent { - - static propTypes = { - value: PropTypes.string.isRequired, - disabled: PropTypes.bool, - intl: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - }; - - handleChange = e => { - this.props.onChange(e.target.value); - } - - handleSubmit = e => { - e.preventDefault(); - this.props.onSubmit(); - } - - handleClick = e => { - e.preventDefault(); - this.props.onSubmit(); - } - - render() { - const { value, disabled, intl } = this.props; - - const label = intl.formatMessage(messages.label); - const create = intl.formatMessage(messages.create); - - return ( -
- - - -
- ); - } - -} diff --git a/app/soapbox/features/lists/components/new_list_form.tsx b/app/soapbox/features/lists/components/new_list_form.tsx new file mode 100644 index 000000000..b6385ecf3 --- /dev/null +++ b/app/soapbox/features/lists/components/new_list_form.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useDispatch } from 'react-redux'; + +import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists'; +import { Button } from 'soapbox/components/ui'; +import { useAppSelector } from 'soapbox/hooks'; + +const messages = defineMessages({ + label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' }, + title: { id: 'lists.new.create', defaultMessage: 'Add list' }, + create: { id: 'lists.new.create_title', defaultMessage: 'Create' }, +}); + +const NewListForm: React.FC = () => { + const dispatch = useDispatch(); + const intl = useIntl(); + + const value = useAppSelector((state) => state.listEditor.get('title')); + const disabled = useAppSelector((state) => !!state.listEditor.get('isSubmitting')); + + const handleChange = (e: React.ChangeEvent) => { + dispatch(changeListEditorTitle(e.target.value)); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + dispatch(submitListEditor(true)); + }; + + const label = intl.formatMessage(messages.label); + const create = intl.formatMessage(messages.create); + + return ( +
+ + + +
+ ); +}; + +export default NewListForm; diff --git a/app/soapbox/features/lists/index.js b/app/soapbox/features/lists/index.js deleted file mode 100644 index 03521286d..000000000 --- a/app/soapbox/features/lists/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; - -import { fetchLists } from 'soapbox/actions/lists'; -import ScrollableList from 'soapbox/components/scrollable_list'; -import { Spinner } from 'soapbox/components/ui'; -import { CardHeader, CardTitle } from 'soapbox/components/ui'; - - -import Column from '../ui/components/column'; -import ColumnLink from '../ui/components/column_link'; - -import NewListForm from './components/new_list_form'; - -const messages = defineMessages({ - heading: { id: 'column.lists', defaultMessage: 'Lists' }, - subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' }, - add: { id: 'lists.new.create', defaultMessage: 'Add list' }, -}); - -const getOrderedLists = createSelector([state => state.get('lists')], lists => { - if (!lists) { - return lists; - } - - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); -}); - -const mapStateToProps = state => ({ - lists: getOrderedLists(state), -}); - -export default @connect(mapStateToProps) -@injectIntl -class Lists extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - lists: ImmutablePropTypes.list, - intl: PropTypes.object.isRequired, - }; - - componentDidMount() { - this.props.dispatch(fetchLists()); - } - - render() { - const { intl, lists } = this.props; - - if (!lists) { - return ( - - - - ); - } - - const emptyMessage = ; - - return ( - -
- - - - -
- - - - - {lists.map(list => - , - )} - -
- ); - } - -} diff --git a/app/soapbox/features/lists/index.tsx b/app/soapbox/features/lists/index.tsx new file mode 100644 index 000000000..5b9a0e936 --- /dev/null +++ b/app/soapbox/features/lists/index.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { useEffect } from 'react'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import { useDispatch } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { fetchLists } from 'soapbox/actions/lists'; +import ScrollableList from 'soapbox/components/scrollable_list'; +import { Spinner } from 'soapbox/components/ui'; +import { CardHeader, CardTitle } from 'soapbox/components/ui'; +import { useAppSelector } from 'soapbox/hooks'; + +import Column from '../ui/components/column'; +import ColumnLink from '../ui/components/column_link'; + +import NewListForm from './components/new_list_form'; + +import type { RootState } from 'soapbox/store'; + +const messages = defineMessages({ + heading: { id: 'column.lists', defaultMessage: 'Lists' }, + subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' }, + add: { id: 'lists.new.create', defaultMessage: 'Add list' }, +}); + +const getOrderedLists = createSelector([(state: RootState) => state.lists], lists => { + if (!lists) { + return lists; + } + + return lists.toList().filter((item) => !!item).sort((a: any, b: any) => a.get('title').localeCompare(b.get('title'))); +}); + +const Lists: React.FC = () => { + const dispatch = useDispatch(); + const intl = useIntl(); + + const lists = useAppSelector((state) => getOrderedLists(state)); + + useEffect(() => { + dispatch(fetchLists()); + }, []); + + if (!lists) { + return ( + + + + ); + } + + const emptyMessage = ; + + return ( + +
+ + + + +
+ + + + + {lists.map((list: any) => + , + )} + +
+ ); +}; + +export default Lists; diff --git a/app/soapbox/normalizers/index.ts b/app/soapbox/normalizers/index.ts index 3251669de..b6d3ba8de 100644 --- a/app/soapbox/normalizers/index.ts +++ b/app/soapbox/normalizers/index.ts @@ -5,6 +5,7 @@ export { ChatRecord, normalizeChat } from './chat'; export { ChatMessageRecord, normalizeChatMessage } from './chat_message'; export { EmojiRecord, normalizeEmoji } from './emoji'; export { InstanceRecord, normalizeInstance } from './instance'; +export { ListRecord, normalizeList } from './list'; export { MentionRecord, normalizeMention } from './mention'; export { NotificationRecord, normalizeNotification } from './notification'; export { PollRecord, PollOptionRecord, normalizePoll } from './poll'; diff --git a/app/soapbox/normalizers/list.ts b/app/soapbox/normalizers/list.ts new file mode 100644 index 000000000..ed3ed1c94 --- /dev/null +++ b/app/soapbox/normalizers/list.ts @@ -0,0 +1,19 @@ +/** + * List normalizer: + * Converts API lists into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/list/} + */ +import { Record as ImmutableRecord, Map as ImmutableMap, fromJS } from 'immutable'; + +// https://docs.joinmastodon.org/entities/list/ +export const ListRecord = ImmutableRecord({ + id: '', + title: '', + replies_policy: null as 'followed' | 'list' | 'none' | null, +}); + +export const normalizeList = (list: Record) => { + return ListRecord( + ImmutableMap(fromJS(list)), + ); +}; diff --git a/app/soapbox/reducers/list_adder.js b/app/soapbox/reducers/list_adder.ts similarity index 51% rename from app/soapbox/reducers/list_adder.js rename to app/soapbox/reducers/list_adder.ts index 0f61273aa..c045a4d53 100644 --- a/app/soapbox/reducers/list_adder.js +++ b/app/soapbox/reducers/list_adder.ts @@ -1,4 +1,5 @@ -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { List as ImmutableList, Record as ImmutableRecord } from 'immutable'; +import { AnyAction } from 'redux'; import { LIST_ADDER_RESET, @@ -10,20 +11,24 @@ import { LIST_EDITOR_REMOVE_SUCCESS, } from '../actions/lists'; -const initialState = ImmutableMap({ - accountId: null, - - lists: ImmutableMap({ - items: ImmutableList(), - loaded: false, - isLoading: false, - }), +const ListsRecord = ImmutableRecord({ + items: ImmutableList(), + loaded: false, + isLoading: false, }); -export default function listAdderReducer(state = initialState, action) { +const ReducerRecord = ImmutableRecord({ + accountId: null as string | null, + + lists: ListsRecord(), +}); + +type State = ReturnType; + +export default function listAdderReducer(state: State = ReducerRecord(), action: AnyAction) { switch(action.type) { case LIST_ADDER_RESET: - return initialState; + return ReducerRecord(); case LIST_ADDER_SETUP: return state.withMutations(map => { map.set('accountId', action.account.get('id')); @@ -36,12 +41,12 @@ export default function listAdderReducer(state = initialState, action) { return state.update('lists', lists => lists.withMutations(map => { map.set('isLoading', false); map.set('loaded', true); - map.set('items', ImmutableList(action.lists.map(item => item.id))); + map.set('items', ImmutableList(action.lists.map((item: { id: string }) => item.id))); })); case LIST_EDITOR_ADD_SUCCESS: - return state.updateIn(['lists', 'items'], list => list.unshift(action.listId)); + return state.updateIn(['lists', 'items'], list => (list as ImmutableList).unshift(action.listId)); case LIST_EDITOR_REMOVE_SUCCESS: - return state.updateIn(['lists', 'items'], list => list.filterNot(item => item === action.listId)); + return state.updateIn(['lists', 'items'], list => (list as ImmutableList).filterNot(item => item === action.listId)); default: return state; } diff --git a/app/soapbox/reducers/list_editor.js b/app/soapbox/reducers/list_editor.ts similarity index 68% rename from app/soapbox/reducers/list_editor.js rename to app/soapbox/reducers/list_editor.ts index ceceb27c7..3c1611a92 100644 --- a/app/soapbox/reducers/list_editor.js +++ b/app/soapbox/reducers/list_editor.ts @@ -1,4 +1,5 @@ -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { List as ImmutableList, Record as ImmutableRecord } from 'immutable'; +import { AnyAction } from 'redux'; import { LIST_CREATE_REQUEST, @@ -20,28 +21,34 @@ import { LIST_EDITOR_REMOVE_SUCCESS, } from '../actions/lists'; -const initialState = ImmutableMap({ - listId: null, +const AccountsRecord = ImmutableRecord({ + items: ImmutableList(), + loaded: false, + isLoading: false, +}); + +const SuggestionsRecord = ImmutableRecord({ + value: '', + items: ImmutableList(), +}); + +const ReducerRecord = ImmutableRecord({ + listId: null as string | null, isSubmitting: false, isChanged: false, title: '', - accounts: ImmutableMap({ - items: ImmutableList(), - loaded: false, - isLoading: false, - }), + accounts: AccountsRecord(), - suggestions: ImmutableMap({ - value: '', - items: ImmutableList(), - }), + suggestions: SuggestionsRecord(), }); -export default function listEditorReducer(state = initialState, action) { +type State = ReturnType; + +export default function listEditorReducer(state: State = ReducerRecord(), action: AnyAction) { switch(action.type) { case LIST_EDITOR_RESET: - return initialState; + return ReducerRecord(); case LIST_EDITOR_SETUP: return state.withMutations(map => { map.set('listId', action.list.get('id')); @@ -76,21 +83,21 @@ export default function listEditorReducer(state = initialState, action) { return state.update('accounts', accounts => accounts.withMutations(map => { map.set('isLoading', false); map.set('loaded', true); - map.set('items', ImmutableList(action.accounts.map(item => item.id))); + map.set('items', ImmutableList(action.accounts.map((item: { id: string }) => item.id))); })); case LIST_EDITOR_SUGGESTIONS_CHANGE: return state.setIn(['suggestions', 'value'], action.value); case LIST_EDITOR_SUGGESTIONS_READY: - return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id))); + return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item: { id: string }) => item.id))); case LIST_EDITOR_SUGGESTIONS_CLEAR: return state.update('suggestions', suggestions => suggestions.withMutations(map => { map.set('items', ImmutableList()); map.set('value', ''); })); case LIST_EDITOR_ADD_SUCCESS: - return state.updateIn(['accounts', 'items'], list => list.unshift(action.accountId)); + return state.updateIn(['accounts', 'items'], list => (list as ImmutableList).unshift(action.accountId)); case LIST_EDITOR_REMOVE_SUCCESS: - return state.updateIn(['accounts', 'items'], list => list.filterNot(item => item === action.accountId)); + return state.updateIn(['accounts', 'items'], list => (list as ImmutableList).filterNot((item) => item === action.accountId)); default: return state; } diff --git a/app/soapbox/reducers/lists.js b/app/soapbox/reducers/lists.js deleted file mode 100644 index 2a797772b..000000000 --- a/app/soapbox/reducers/lists.js +++ /dev/null @@ -1,38 +0,0 @@ -import { Map as ImmutableMap, fromJS } from 'immutable'; - -import { - LIST_FETCH_SUCCESS, - LIST_FETCH_FAIL, - LISTS_FETCH_SUCCESS, - LIST_CREATE_SUCCESS, - LIST_UPDATE_SUCCESS, - LIST_DELETE_SUCCESS, -} from '../actions/lists'; - -const initialState = ImmutableMap(); - -const normalizeList = (state, list) => state.set(list.id, fromJS(list)); - -const normalizeLists = (state, lists) => { - lists.forEach(list => { - state = normalizeList(state, list); - }); - - return state; -}; - -export default function lists(state = initialState, action) { - switch(action.type) { - case LIST_FETCH_SUCCESS: - case LIST_CREATE_SUCCESS: - case LIST_UPDATE_SUCCESS: - return normalizeList(state, action.list); - case LISTS_FETCH_SUCCESS: - return normalizeLists(state, action.lists); - case LIST_DELETE_SUCCESS: - case LIST_FETCH_FAIL: - return state.set(action.id, false); - default: - return state; - } -} diff --git a/app/soapbox/reducers/lists.ts b/app/soapbox/reducers/lists.ts new file mode 100644 index 000000000..e0ab4db97 --- /dev/null +++ b/app/soapbox/reducers/lists.ts @@ -0,0 +1,46 @@ +import { Map as ImmutableMap } from 'immutable'; +import { AnyAction } from 'redux'; + +import { + LIST_FETCH_SUCCESS, + LIST_FETCH_FAIL, + LISTS_FETCH_SUCCESS, + LIST_CREATE_SUCCESS, + LIST_UPDATE_SUCCESS, + LIST_DELETE_SUCCESS, +} from 'soapbox/actions/lists'; +import { normalizeList } from 'soapbox/normalizers'; + +type ListRecord = ReturnType; +type APIEntity = Record; +type APIEntities = Array; + +type State = ImmutableMap; + +const initialState: State = ImmutableMap(); + +const importList = (state: State, list: APIEntity) => state.set(list.id, normalizeList(list)); + +const importLists = (state: State, lists: APIEntities) => { + lists.forEach(list => { + state = importList(state, list); + }); + + return state; +}; + +export default function lists(state: State = initialState, action: AnyAction) { + switch(action.type) { + case LIST_FETCH_SUCCESS: + case LIST_CREATE_SUCCESS: + case LIST_UPDATE_SUCCESS: + return importList(state, action.list); + case LISTS_FETCH_SUCCESS: + return importLists(state, action.lists); + case LIST_DELETE_SUCCESS: + case LIST_FETCH_FAIL: + return state.set(action.id, false); + default: + return state; + } +}