Refactor accounts map reducer to TypeScript
This commit is contained in:
parent
db269a4c0a
commit
fbe899fe14
@ -92,34 +92,6 @@ export function fetchAccount(id) {
|
||||
};
|
||||
}
|
||||
|
||||
export const lookupAccount = acct => (dispatch) => {
|
||||
dispatch(lookupAccountRequest(acct));
|
||||
|
||||
api().get('/api/v1/accounts/lookup', { params: { acct } }).then(response => {
|
||||
dispatch(fetchRelationships([response.data.id]));
|
||||
dispatch(importFetchedAccount(response.data));
|
||||
dispatch(lookupAccountSuccess());
|
||||
}).catch(error => {
|
||||
dispatch(lookupAccountFail(acct, error));
|
||||
});
|
||||
};
|
||||
|
||||
export const lookupAccountRequest = (acct) => ({
|
||||
type: ACCOUNT_LOOKUP_REQUEST,
|
||||
acct,
|
||||
});
|
||||
|
||||
export const lookupAccountSuccess = () => ({
|
||||
type: ACCOUNT_LOOKUP_SUCCESS,
|
||||
});
|
||||
|
||||
export const lookupAccountFail = (acct, error) => ({
|
||||
type: ACCOUNT_LOOKUP_FAIL,
|
||||
acct,
|
||||
error,
|
||||
skipAlert: true,
|
||||
});
|
||||
|
||||
export function fetchAccountRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_FETCH_REQUEST,
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import { apiLookupAccount } from 'mastodon/api/accounts';
|
||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
|
||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
import { importFetchedAccount } from './importer';
|
||||
|
||||
export const revealAccount = createAction<{
|
||||
id: string;
|
||||
}>('accounts/revealAccount');
|
||||
|
||||
export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
|
||||
'accounts/importAccounts',
|
||||
);
|
||||
|
||||
function actionWithSkipLoadingTrue<Args extends object>(args: Args) {
|
||||
return {
|
||||
payload: {
|
||||
@ -95,3 +94,14 @@ export const fetchRelationshipsSuccess = createAction(
|
||||
'relationships/fetch/SUCCESS',
|
||||
actionWithSkipLoadingTrue<{ relationships: ApiRelationshipJSON[] }>,
|
||||
);
|
||||
|
||||
export const lookupAccount = createDataLoadingThunk(
|
||||
'accounts/lookup',
|
||||
({ acct }: { acct: string }) => apiLookupAccount(acct),
|
||||
(data, { dispatch }) => {
|
||||
dispatch(importFetchedAccount(data));
|
||||
//dispatch(fetchRelationships([data.id]));
|
||||
|
||||
return data;
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,89 +0,0 @@
|
||||
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
||||
|
||||
import { importAccounts } from '../accounts_typed';
|
||||
|
||||
import { normalizeStatus } from './normalizer';
|
||||
import { importPolls } from './polls';
|
||||
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
export const FILTERS_IMPORT = 'FILTERS_IMPORT';
|
||||
|
||||
function pushUnique(array, object) {
|
||||
if (array.every(element => element.id !== object.id)) {
|
||||
array.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
export function importStatus(status) {
|
||||
return { type: STATUS_IMPORT, status };
|
||||
}
|
||||
|
||||
export function importStatuses(statuses) {
|
||||
return { type: STATUSES_IMPORT, statuses };
|
||||
}
|
||||
|
||||
export function importFilters(filters) {
|
||||
return { type: FILTERS_IMPORT, filters };
|
||||
}
|
||||
|
||||
export function importFetchedAccount(account) {
|
||||
return importFetchedAccounts([account]);
|
||||
}
|
||||
|
||||
export function importFetchedAccounts(accounts) {
|
||||
const normalAccounts = [];
|
||||
|
||||
function processAccount(account) {
|
||||
pushUnique(normalAccounts, account);
|
||||
|
||||
if (account.moved) {
|
||||
processAccount(account.moved);
|
||||
}
|
||||
}
|
||||
|
||||
accounts.forEach(processAccount);
|
||||
|
||||
return importAccounts({ accounts: normalAccounts });
|
||||
}
|
||||
|
||||
export function importFetchedStatus(status) {
|
||||
return importFetchedStatuses([status]);
|
||||
}
|
||||
|
||||
export function importFetchedStatuses(statuses) {
|
||||
return (dispatch, getState) => {
|
||||
const accounts = [];
|
||||
const normalStatuses = [];
|
||||
const polls = [];
|
||||
const filters = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
|
||||
pushUnique(accounts, status.account);
|
||||
|
||||
if (status.filtered) {
|
||||
status.filtered.forEach(result => pushUnique(filters, result.filter));
|
||||
}
|
||||
|
||||
if (status.reblog?.id) {
|
||||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
if (status.poll?.id) {
|
||||
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls.get(status.poll.id)));
|
||||
}
|
||||
|
||||
if (status.card) {
|
||||
status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account));
|
||||
}
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
|
||||
dispatch(importPolls({ polls }));
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
dispatch(importFilters(filters));
|
||||
};
|
||||
}
|
||||
117
app/javascript/mastodon/actions/importer/index.ts
Normal file
117
app/javascript/mastodon/actions/importer/index.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { ApiStatusJSON, ApiFilterJSON } from 'mastodon/api_types/statuses';
|
||||
import type { Poll } from 'mastodon/models/poll';
|
||||
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
||||
import type { AppDispatch, RootState } from 'mastodon/store';
|
||||
|
||||
import { normalizeStatus } from './normalizer';
|
||||
import { importPolls } from './polls';
|
||||
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
export const FILTERS_IMPORT = 'FILTERS_IMPORT';
|
||||
|
||||
export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
|
||||
'accounts/importAccounts',
|
||||
);
|
||||
|
||||
interface Identifiable {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const pushUnique = (array: Identifiable[], object: Identifiable) => {
|
||||
if (array.every((element) => element.id !== object.id)) {
|
||||
array.push(object);
|
||||
}
|
||||
};
|
||||
|
||||
export const importStatus = (status: ApiStatusJSON) => {
|
||||
return { type: STATUS_IMPORT, status };
|
||||
};
|
||||
|
||||
export const importStatuses = (statuses: ApiStatusJSON[]) => {
|
||||
return { type: STATUSES_IMPORT, statuses };
|
||||
};
|
||||
|
||||
export const importFilters = (filters: unknown[]) => {
|
||||
return { type: FILTERS_IMPORT, filters };
|
||||
};
|
||||
|
||||
export const importFetchedAccount = (account: ApiAccountJSON) => {
|
||||
return importFetchedAccounts([account]);
|
||||
};
|
||||
|
||||
export const importFetchedAccounts = (accounts: ApiAccountJSON[]) => {
|
||||
const normalAccounts: ApiAccountJSON[] = [];
|
||||
|
||||
function processAccount(account: ApiAccountJSON) {
|
||||
pushUnique(normalAccounts, account);
|
||||
|
||||
if (account.moved) {
|
||||
processAccount(account.moved);
|
||||
}
|
||||
}
|
||||
|
||||
accounts.forEach(processAccount);
|
||||
|
||||
return importAccounts({ accounts: normalAccounts });
|
||||
};
|
||||
|
||||
export const importFetchedStatus = (status: ApiStatusJSON) => {
|
||||
return importFetchedStatuses([status]);
|
||||
};
|
||||
|
||||
export const importFetchedStatuses = (statuses: ApiStatusJSON[]) => {
|
||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const accounts: ApiAccountJSON[] = [];
|
||||
const normalStatuses: ApiStatusJSON[] = [];
|
||||
const polls: Poll[] = [];
|
||||
const filters: ApiFilterJSON[] = [];
|
||||
|
||||
function processStatus(status: ApiStatusJSON) {
|
||||
pushUnique(
|
||||
normalStatuses,
|
||||
normalizeStatus(
|
||||
status,
|
||||
getState().statuses.get(status.id),
|
||||
) as ApiStatusJSON,
|
||||
);
|
||||
pushUnique(accounts, status.account);
|
||||
|
||||
if (status.filtered) {
|
||||
status.filtered.forEach((result) => {
|
||||
pushUnique(filters, result.filter);
|
||||
});
|
||||
}
|
||||
|
||||
if (status.reblog?.id) {
|
||||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
if (status.poll?.id) {
|
||||
pushUnique(
|
||||
polls,
|
||||
createPollFromServerJSON(
|
||||
status.poll,
|
||||
getState().polls.get(status.poll.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (status.card) {
|
||||
status.card.authors.forEach((author) => {
|
||||
if (author.account) pushUnique(accounts, author.account);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
|
||||
dispatch(importPolls({ polls }));
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
dispatch(importFilters(filters));
|
||||
};
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
import { apiRequestPost } from 'mastodon/api';
|
||||
import { apiRequestPost, apiRequestGet } from 'mastodon/api';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
|
||||
|
||||
export const apiSubmitAccountNote = (id: string, value: string) =>
|
||||
@ -18,3 +19,6 @@ export const apiFollowAccount = (
|
||||
|
||||
export const apiUnfollowAccount = (id: string) =>
|
||||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/unfollow`);
|
||||
|
||||
export const apiLookupAccount = (acct: string) =>
|
||||
apiRequestGet<ApiAccountJSON>('v1/accounts/lookup', { acct });
|
||||
|
||||
@ -8,7 +8,8 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { lookupAccount } from 'mastodon/actions/accounts_typed';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
@ -101,7 +102,10 @@ export const AccountGallery: React.FC<{
|
||||
const accountId = useAppSelector(
|
||||
(state) =>
|
||||
id ??
|
||||
(state.accounts_map.get(normalizeForLookup(acct)) as string | undefined),
|
||||
(acct &&
|
||||
(state.accounts_map.get(normalizeForLookup(acct)) as
|
||||
| string
|
||||
| undefined)),
|
||||
);
|
||||
const attachments = useAppSelector((state) =>
|
||||
accountId
|
||||
@ -140,8 +144,8 @@ export const AccountGallery: React.FC<{
|
||||
| undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountId) {
|
||||
dispatch(lookupAccount(acct));
|
||||
if (!accountId && acct) {
|
||||
void dispatch(lookupAccount({ acct }));
|
||||
}
|
||||
}, [dispatch, accountId, acct]);
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { lookupAccount } from 'mastodon/actions/accounts_typed';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
@ -14,7 +15,7 @@ import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
||||
import { fetchAccount } from '../../actions/accounts';
|
||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
||||
import { ColumnBackButton } from '../../components/column_back_button';
|
||||
@ -125,7 +126,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
dispatch(lookupAccount({ acct }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +136,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
dispatch(lookupAccount({ acct }));
|
||||
} else if (prevProps.params.tagged !== tagged) {
|
||||
if (!withReplies) {
|
||||
dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
|
||||
|
||||
@ -8,6 +8,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { lookupAccount } from 'mastodon/actions/accounts_typed';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
||||
@ -17,7 +18,6 @@ import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import {
|
||||
lookupAccount,
|
||||
fetchAccount,
|
||||
fetchFollowers,
|
||||
expandFollowers,
|
||||
@ -104,7 +104,7 @@ class Followers extends ImmutablePureComponent {
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
dispatch(lookupAccount({ acct }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ class Followers extends ImmutablePureComponent {
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
dispatch(lookupAccount({ acct }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { lookupAccount } from 'mastodon/actions/accounts_typed';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
||||
@ -17,7 +18,6 @@ import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import {
|
||||
lookupAccount,
|
||||
fetchAccount,
|
||||
fetchFollowing,
|
||||
expandFollowing,
|
||||
@ -104,7 +104,7 @@ class Following extends ImmutablePureComponent {
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
dispatch(lookupAccount({ acct }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ class Following extends ImmutablePureComponent {
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
dispatch(lookupAccount({ acct }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,9 +4,9 @@ import { Map as ImmutableMap } from 'immutable';
|
||||
import {
|
||||
followAccountSuccess,
|
||||
unfollowAccountSuccess,
|
||||
importAccounts,
|
||||
revealAccount,
|
||||
} from 'mastodon/actions/accounts_typed';
|
||||
import { importAccounts } from 'mastodon/actions/importer';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { ACCOUNT_LOOKUP_FAIL } from '../actions/accounts';
|
||||
import { importAccounts } from '../actions/accounts_typed';
|
||||
import { domain } from '../initial_state';
|
||||
|
||||
const pattern = new RegExp(`@${domain}$`, 'gi');
|
||||
|
||||
export const normalizeForLookup = str =>
|
||||
str.toLowerCase().replace(pattern, '');
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function accountsMap(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_LOOKUP_FAIL:
|
||||
return action.error?.response?.status === 404 ? state.set(normalizeForLookup(action.acct), null) : state;
|
||||
case importAccounts.type:
|
||||
return state.withMutations(map => action.payload.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id)));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
35
app/javascript/mastodon/reducers/accounts_map.ts
Normal file
35
app/javascript/mastodon/reducers/accounts_map.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { Reducer } from '@reduxjs/toolkit';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { lookupAccount } from 'mastodon/actions/accounts_typed';
|
||||
import { importAccounts } from 'mastodon/actions/importer';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
|
||||
const pattern = new RegExp(`@${domain}$`, 'gi');
|
||||
|
||||
export const normalizeForLookup = (str: string) =>
|
||||
str.toLowerCase().replace(pattern, '');
|
||||
|
||||
const initialState = ImmutableMap<string, string | null>();
|
||||
|
||||
export const accountsMapReducer: Reducer<typeof initialState> = (
|
||||
state = initialState,
|
||||
action,
|
||||
) => {
|
||||
if (lookupAccount.rejected.match(action)) {
|
||||
return action.error instanceof AxiosError &&
|
||||
action.error.response?.status === 404
|
||||
? state.set(normalizeForLookup(action.meta.arg.acct), null)
|
||||
: state;
|
||||
} else if (importAccounts.match(action)) {
|
||||
return state.withMutations((map) => {
|
||||
action.payload.accounts.forEach((account) =>
|
||||
map.set(normalizeForLookup(account.acct), account.id),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@ -4,7 +4,7 @@ import { loadingBarReducer } from 'react-redux-loading-bar';
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
|
||||
import { accountsReducer } from './accounts';
|
||||
import accounts_map from './accounts_map';
|
||||
import { accountsMapReducer } from './accounts_map';
|
||||
import alerts from './alerts';
|
||||
import announcements from './announcements';
|
||||
import { composeReducer } from './compose';
|
||||
@ -52,7 +52,7 @@ const reducers = {
|
||||
domain_lists,
|
||||
status_lists,
|
||||
accounts: accountsReducer,
|
||||
accounts_map,
|
||||
accounts_map: accountsMapReducer,
|
||||
statuses,
|
||||
relationships: relationshipsReducer,
|
||||
settings,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user