pl-fe: remove some immutable usage

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-11-10 23:10:54 +01:00
parent 66fbdf2ad9
commit 865130c55a
15 changed files with 197 additions and 169 deletions

View file

@ -59,7 +59,7 @@ const Aliases = () => {
</CardHeader>
<Search />
{
loaded && searchAccountIds.size === 0 ? (
loaded && searchAccountIds.length === 0 ? (
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.aliases.suggestions' defaultMessage='There are no account suggestions available for the provided term.' />
</div>

View file

@ -1,4 +1,3 @@
import { Map as ImmutableMap } from 'immutable';
import React, { useState, useEffect } from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
@ -19,7 +18,7 @@ interface ICaptchaField {
name?: string;
value: string;
onChange?: React.ChangeEventHandler<HTMLInputElement>;
onFetch?: (captcha: ImmutableMap<string, any>) => void;
onFetch?: (captcha: Record<string, any>) => void;
onFetchFail?: (error: Error) => void;
onClick?: React.MouseEventHandler;
refreshInterval?: number;
@ -38,12 +37,11 @@ const CaptchaField: React.FC<ICaptchaField> = ({
}) => {
const dispatch = useAppDispatch();
const [captcha, setCaptcha] = useState(ImmutableMap<string, any>());
const [captcha, setCaptcha] = useState<Record<string, any>>({});
const [refresh, setRefresh] = useState<NodeJS.Timeout | undefined>(undefined);
const getCaptcha = () => {
dispatch(fetchCaptcha()).then((response) => {
const captcha = ImmutableMap(response);
dispatch(fetchCaptcha()).then((captcha) => {
setCaptcha(captcha);
onFetch(captcha);
}).catch((error: Error) => {
@ -98,7 +96,7 @@ const CaptchaField: React.FC<ICaptchaField> = ({
};
interface INativeCaptchaField {
captcha: ImmutableMap<string, any>;
captcha: Record<string, any>;
onChange: React.ChangeEventHandler<HTMLInputElement>;
onClick: React.MouseEventHandler;
name?: string;
@ -111,7 +109,7 @@ const NativeCaptchaField: React.FC<INativeCaptchaField> = ({ captcha, onChange,
return (
<Stack space={2}>
<div className='flex w-full items-center justify-center rounded-md border border-solid border-gray-300 bg-white dark:border-gray-600'>
<img alt={intl.formatMessage(messages.captcha)} src={captcha.get('url')} onClick={onClick} />
<img alt={intl.formatMessage(messages.captcha)} src={captcha.url} onClick={onClick} />
</div>
<Input

View file

@ -1,4 +1,3 @@
import { Map as ImmutableMap } from 'immutable';
import debounce from 'lodash/debounce';
import React, { useState, useRef, useCallback } from 'react';
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
@ -221,12 +220,12 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
refreshCaptcha();
};
const onFetchCaptcha = (captcha: ImmutableMap<string, any>) => {
const onFetchCaptcha = (captcha: Record<string, any>) => {
setCaptchaLoading(false);
setParams(params => ({
...params,
captcha_token: captcha.get('token'),
captcha_answer_data: captcha.get('answer_data'),
captcha_token: captcha.token,
captcha_answer_data: captcha.answer_data,
}));
};

View file

@ -2,12 +2,10 @@
// See: https://github.com/spothq/cryptocurrency-icons/blob/master/manifest.json
import manifest from 'cryptocurrency-icons/manifest.json';
import { List as ImmutableList, Map as ImmutableMap, fromJS } from 'immutable';
const manifestMap = (fromJS(manifest) as ImmutableList<ImmutableMap<string, string>>).reduce(
(acc: ImmutableMap<string, ImmutableMap<string, string>>, entry: ImmutableMap<string, string>) =>
acc.set(entry.get('symbol')!.toLowerCase(), entry),
ImmutableMap(),
).toJS();
const manifestMap = manifest.reduce((acc: Record<string, typeof manifest[0]>, entry) => {
acc[entry.symbol.toLowerCase()] = entry;
return acc;
}, {});
export { manifestMap as default };

View file

@ -11,8 +11,6 @@ import { federationRestrictionsDisclosed } from 'pl-fe/utils/state';
import RestrictedInstance from './components/restricted-instance';
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
const messages = defineMessages({
heading: { id: 'column.federation_restrictions', defaultMessage: 'Federation Restrictions' },
boxTitle: { id: 'federation_restrictions.explanation_box.title', defaultMessage: 'Instance-specific policies' },
@ -27,7 +25,7 @@ const FederationRestrictions = () => {
const getHosts = useCallback(makeGetHosts(), []);
const hosts = useAppSelector((state) => getHosts(state)) as ImmutableOrderedSet<string>;
const hosts = useAppSelector((state) => getHosts(state));
const disclosed = useAppSelector((state) => federationRestrictionsDisclosed(state));
const [explanationBoxExpanded, setExplanationBoxExpanded] = useState(true);

View file

@ -1,5 +1,4 @@
import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useRef } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -31,8 +30,8 @@ const messages = defineMessages({
});
const getNotifications = createSelector([
(state: RootState) => state.notifications.items.toList(),
], (notifications) => notifications.filter(item => item !== null));
(state: RootState) => state.notifications.items.toArray(),
], (notifications) => notifications.map(([_, notification]) => notification).filter(item => item !== null));
const Notifications = () => {
const dispatch = useAppDispatch();
@ -47,7 +46,7 @@ const Notifications = () => {
const hasMore = useAppSelector(state => state.notifications.hasMore);
const totalQueuedNotificationsCount = useAppSelector(state => state.notifications.totalQueuedNotificationsCount || 0);
const scrollableContentRef = useRef<ImmutableList<JSX.Element> | null>(null);
const scrollableContentRef = useRef<Array<JSX.Element> | null>(null);
// const handleLoadGap = (maxId) => {
// dispatch(expandNotifications({ maxId }));
@ -105,7 +104,7 @@ const Notifications = () => {
? <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
: <FormattedMessage id='empty_column.notifications_filtered' defaultMessage="You don't have any notifications of this type yet." />;
let scrollableContent: ImmutableList<JSX.Element> | null = null;
let scrollableContent: Array<JSX.Element> | null = null;
const filterBarContainer = showFilterBar
? (<FilterBar />)
@ -113,7 +112,7 @@ const Notifications = () => {
if (isLoading && scrollableContentRef.current) {
scrollableContent = scrollableContentRef.current;
} else if (notifications.size > 0 || hasMore) {
} else if (notifications.length > 0 || hasMore) {
scrollableContent = notifications.map((item) => (
<Notification
key={item.group_key}
@ -131,7 +130,7 @@ const Notifications = () => {
const scrollContainer = (
<ScrollableList
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
showLoading={isLoading && notifications.length === 0}
hasMore={hasMore}
emptyMessage={emptyMessage}
placeholderComponent={PlaceholderNotification}
@ -139,7 +138,7 @@ const Notifications = () => {
onLoadMore={handleLoadOlder}
onScroll={handleScroll}
listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', {
'animate-pulse': notifications.size === 0,
'animate-pulse': notifications.length === 0,
})}
>
{scrollableContent!}

View file

@ -1,4 +1,3 @@
import { Record as ImmutableRecord } from 'immutable';
import { MediaAttachment } from 'pl-api';
import React, { useState } from 'react';
@ -7,12 +6,12 @@ interface IPlaceholderMediaGallery {
defaultWidth?: number;
}
const SizeData = ImmutableRecord({
style: {} as React.CSSProperties,
itemsDimensions: [] as Record<string, string>[],
size: 1 as number,
width: 0 as number,
});
interface SizeData {
style: React.CSSProperties;
itemsDimensions: Record<string, string>[];
size: number;
width: number;
}
const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, defaultWidth }) => {
const [width, setWidth] = useState(defaultWidth);
@ -23,7 +22,7 @@ const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, de
}
};
const getSizeData = (size: number) => {
const getSizeData = (size: number): SizeData => {
const style: React.CSSProperties = {};
let itemsDimensions: Record<string, string>[] = [];
@ -59,12 +58,12 @@ const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, de
];
}
return SizeData({
return {
style,
itemsDimensions,
size,
width,
});
width: width || 0,
};
};
const renderItem = (dimensions: Record<string, string>, i: number) => {

View file

@ -1,4 +1,3 @@
import { Set as ImmutableSet } from 'immutable';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -106,7 +105,7 @@ const Preferences = () => {
};
const onSelectMultiple = (selectedList: string[], path: string[]) => {
dispatch(changeSetting(path, ImmutableSet(selectedList.sort((a, b) => a.localeCompare(b))), { showAlert: true }));
dispatch(changeSetting(path, selectedList.toSorted((a, b) => a.localeCompare(b)), { showAlert: true }));
};
const onToggleChange = (key: string[], checked: boolean) => {

View file

@ -54,7 +54,7 @@ const ListEditorModal: React.FC<BaseModalProps & ListEditorModalProps> = ({ list
<EditListForm />
<br />
{accountIds.size > 0 && (
{accountIds.length > 0 && (
<div>
<CardHeader>
<CardTitle title={intl.formatMessage(messages.removeFromList)} />

View file

@ -1,4 +1,4 @@
import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
import { create } from 'mutative';
import {
ALIASES_SUGGESTIONS_READY,
@ -8,37 +8,52 @@ import {
AliasesAction,
} from '../actions/aliases';
const ReducerRecord = ImmutableRecord({
aliases: ImmutableRecord({
items: ImmutableList<string>(),
interface State {
aliases: {
items: Array<string>;
loaded: boolean;
};
suggestions: {
items: Array<string>;
value: string;
loaded: boolean;
};
}
const initialState: State = {
aliases: {
items: [],
loaded: false,
})(),
suggestions: ImmutableRecord({
items: ImmutableList<string>(),
},
suggestions: {
items: [],
value: '',
loaded: false,
})(),
});
},
};
const aliasesReducer = (state = ReducerRecord(), action: AliasesAction) => {
const aliasesReducer = (state = initialState, action: AliasesAction): State => {
switch (action.type) {
case ALIASES_FETCH_SUCCESS:
return state
.setIn(['aliases', 'items'], action.value);
return create(state, (draft) => {
draft.aliases.items = action.value;
});
case ALIASES_SUGGESTIONS_CHANGE:
return state
.setIn(['suggestions', 'value'], action.value)
.setIn(['suggestions', 'loaded'], false);
return create(state, (draft) => {
draft.suggestions.value = action.value;
draft.suggestions.loaded = false;
});
case ALIASES_SUGGESTIONS_READY:
return state
.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item) => item.id)))
.setIn(['suggestions', 'loaded'], true);
return create(state, (draft) => {
draft.suggestions.items = action.accounts.map((item) => item.id);
draft.suggestions.loaded = true;
});
case ALIASES_SUGGESTIONS_CLEAR:
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
map.set('items', ImmutableList());
map.set('value', '');
map.set('loaded', false);
}));
return create(state, (draft) => {
draft.suggestions.items = [];
draft.suggestions.value = '';
draft.suggestions.loaded = false;
});
default:
return state;
}

View file

@ -1,4 +1,4 @@
import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
import { create } from 'mutative';
import {
LIST_ADDER_RESET,
@ -12,42 +12,54 @@ import {
import type { AnyAction } from 'redux';
const ListsRecord = ImmutableRecord({
items: ImmutableList<string>(),
loaded: false,
isLoading: false,
});
interface State {
accountId: string | null;
lists: {
items: Array<string>;
loaded: boolean;
isLoading: boolean;
};
}
const ReducerRecord = ImmutableRecord({
accountId: null as string | null,
const initialState: State = {
accountId: null,
lists: {
items: [],
loaded: false,
isLoading: false,
},
};
lists: ListsRecord(),
});
type State = ReturnType<typeof ReducerRecord>;
const listAdderReducer = (state: State = ReducerRecord(), action: AnyAction) => {
const listAdderReducer = (state: State = initialState, action: AnyAction): State => {
switch (action.type) {
case LIST_ADDER_RESET:
return ReducerRecord();
return initialState;
case LIST_ADDER_SETUP:
return state.withMutations(map => {
map.set('accountId', action.account.id);
return create(state, (draft) => {
draft.accountId = action.account.id;
});
case LIST_ADDER_LISTS_FETCH_REQUEST:
return state.setIn(['lists', 'isLoading'], true);
return create(state, (draft) => {
draft.lists.isLoading = true;
});
case LIST_ADDER_LISTS_FETCH_FAIL:
return state.setIn(['lists', 'isLoading'], false);
return create(state, (draft) => {
draft.lists.isLoading = false;
});
case LIST_ADDER_LISTS_FETCH_SUCCESS:
return state.update('lists', lists => lists.withMutations(map => {
map.set('isLoading', false);
map.set('loaded', true);
map.set('items', ImmutableList(action.lists.map((item: { id: string }) => item.id)));
}));
return create(state, (draft) => {
draft.lists.isLoading = false;
draft.lists.loaded = true;
draft.lists.items = action.lists.map((item: { id: string }) => item.id);
});
case LIST_EDITOR_ADD_SUCCESS:
return state.updateIn(['lists', 'items'], list => (list as ImmutableList<string>).unshift(action.listId));
return create(state, (draft) => {
draft.lists.items = [action.listId, ...draft.lists.items];
});
case LIST_EDITOR_REMOVE_SUCCESS:
return state.updateIn(['lists', 'items'], list => (list as ImmutableList<string>).filterNot(item => item === action.listId));
return create(state, (draft) => {
draft.lists.items = draft.lists.items.filter(id => id !== action.listId);
});
default:
return state;
}

View file

@ -1,4 +1,4 @@
import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
import { create } from 'mutative';
import {
LIST_CREATE_REQUEST,
@ -22,83 +22,109 @@ import {
import type { AnyAction } from 'redux';
const AccountsRecord = ImmutableRecord({
items: ImmutableList<string>(),
loaded: false,
isLoading: false,
});
interface State {
listId: string | null;
isSubmitting: boolean;
isChanged: boolean;
title: string;
const SuggestionsRecord = ImmutableRecord({
value: '',
items: ImmutableList<string>(),
});
accounts: {
items: Array<string>;
loaded: boolean;
isLoading: boolean;
};
const ReducerRecord = ImmutableRecord({
listId: null as string | null,
suggestions: {
value: string;
items: Array<string>;
};
}
const initialState: State = {
listId: null,
isSubmitting: false,
isChanged: false,
title: '',
accounts: AccountsRecord(),
accounts: {
items: [],
loaded: false,
isLoading: false,
},
suggestions: SuggestionsRecord(),
});
suggestions: {
value: '',
items: [],
},
};
type State = ReturnType<typeof ReducerRecord>;
const listEditorReducer = (state: State = ReducerRecord(), action: AnyAction) => {
const listEditorReducer = (state: State = initialState, action: AnyAction): State => {
switch (action.type) {
case LIST_EDITOR_RESET:
return ReducerRecord();
return initialState;
case LIST_EDITOR_SETUP:
return state.withMutations(map => {
map.set('listId', action.list.id);
map.set('title', action.list.title);
map.set('isSubmitting', false);
return create(state, (draft) => {
draft.listId = action.list.id;
draft.title = action.list.title;
draft.isSubmitting = false;
});
case LIST_EDITOR_TITLE_CHANGE:
return state.withMutations(map => {
map.set('title', action.value);
map.set('isChanged', true);
return create(state, (draft) => {
draft.title = action.value;
draft.isChanged = true;
});
case LIST_CREATE_REQUEST:
case LIST_UPDATE_REQUEST:
return state.withMutations(map => {
map.set('isSubmitting', true);
map.set('isChanged', false);
return create(state, (draft) => {
draft.isSubmitting = true;
draft.isChanged = false;
});
case LIST_CREATE_FAIL:
case LIST_UPDATE_FAIL:
return state.set('isSubmitting', false);
return create(state, (draft) => {
draft.isSubmitting = false;
});
case LIST_CREATE_SUCCESS:
case LIST_UPDATE_SUCCESS:
return state.withMutations(map => {
map.set('isSubmitting', false);
map.set('listId', action.list.id);
return create(state, (draft) => {
draft.isSubmitting = false;
draft.listId = action.list.id;
});
case LIST_ACCOUNTS_FETCH_REQUEST:
return state.setIn(['accounts', 'isLoading'], true);
return create(state, (draft) => {
draft.accounts.isLoading = true;
});
case LIST_ACCOUNTS_FETCH_FAIL:
return state.setIn(['accounts', 'isLoading'], false);
return create(state, (draft) => {
draft.accounts.isLoading = false;
});
case LIST_ACCOUNTS_FETCH_SUCCESS:
return state.update('accounts', accounts => accounts.withMutations(map => {
map.set('isLoading', false);
map.set('loaded', true);
map.set('items', ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
}));
return create(state, (draft) => {
draft.accounts.isLoading = false;
draft.accounts.loaded = true;
draft.accounts.items = action.accounts.map((item: { id: string }) => item.id);
});
case LIST_EDITOR_SUGGESTIONS_CHANGE:
return state.setIn(['suggestions', 'value'], action.value);
return create(state, (draft) => {
draft.suggestions.value = action.value;
});
case LIST_EDITOR_SUGGESTIONS_READY:
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
return create(state, (draft) => {
draft.suggestions.items = 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', '');
}));
return create(state, (draft) => {
draft.suggestions.items = [];
draft.suggestions.value = '';
});
case LIST_EDITOR_ADD_SUCCESS:
return state.updateIn(['accounts', 'items'], list => (list as ImmutableList<string>).unshift(action.accountId));
return create(state, (draft) => {
draft.accounts.items = [action.accountId, ...draft.accounts.items];
});
case LIST_EDITOR_REMOVE_SUCCESS:
return state.updateIn(['accounts', 'items'], list => (list as ImmutableList<string>).filterNot((item) => item === action.accountId));
return create(state, (draft) => {
draft.accounts.items = draft.accounts.items.filter(id => id !== action.accoundId);
});
default:
return state;
}

View file

@ -1,4 +1,3 @@
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import { createSelector } from 'reselect';
// import { getLocale } from 'pl-fe/actions/settings';
@ -262,14 +261,11 @@ const makeGetReport = () => {
const getAuthUserIds = createSelector(
[(state: RootState) => state.auth.users],
authUsers => authUsers.reduce((userIds: ImmutableOrderedSet<string>, authUser) => {
try {
const userId = authUser.id;
return validId(userId) ? userIds.add(userId) : userIds;
} catch {
return userIds;
}
}, ImmutableOrderedSet<string>()));
authUsers => authUsers.reduce((userIds: Array<string>, authUser) => {
const userId = authUser?.id;
if (validId(userId)) userIds.push(userId);
return userIds;
}, []));
const makeGetOtherAccounts = () => createSelector([
(state: RootState) => state.entities[Entities.ACCOUNTS]?.store as EntityStore<Account>,
@ -314,9 +310,7 @@ const makeGetHosts = () =>
createSelector([getSimplePolicy], (simplePolicy) => {
const { accept, reject_deletes, report_removal, ...rest } = simplePolicy;
return Object.values(rest)
.reduce((acc, hosts) => acc.union(hosts), ImmutableOrderedSet())
.sort();
return [...new Set(Object.values(rest).reduce((acc, hosts) => (acc.push(...hosts), acc), []))].toSorted();
});
interface RemoteInstance {

View file

@ -1,5 +1,3 @@
import { List as ImmutableList } from 'immutable';
import { selectAccount, selectOwnAccount } from 'pl-fe/selectors';
import type { RootState } from 'pl-fe/store';
@ -43,19 +41,19 @@ const getAccessToken = (state: RootState) => {
const getAuthUserId = (state: RootState) => {
const me = state.auth.me;
return ImmutableList([
return [
state.auth.users.get(me!)?.id,
me,
].filter(id => id)).find(validId);
].filter(id => id).find(validId);
};
const getAuthUserUrl = (state: RootState) => {
const me = state.auth.me;
return ImmutableList([
return [
state.auth.users.get(me!)?.url,
me,
].filter(url => url)).find(isURL);
].filter(url => url).find(isURL);
};
/** Get the VAPID public key. */

View file

@ -1,5 +1,3 @@
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import type { Account } from 'pl-fe/normalizers/account';
/** Convert a plain tag into a badge. */
@ -17,15 +15,10 @@ interface TagDiff {
}
/** Returns the differences between two sets of tags. */
const getTagDiff = (oldTags: string[], newTags: string[]): TagDiff => {
const o = ImmutableOrderedSet(oldTags);
const n = ImmutableOrderedSet(newTags);
return {
added: n.subtract(o).toArray(),
removed: o.subtract(n).toArray(),
};
};
const getTagDiff = (oldTags: string[], newTags: string[]): TagDiff => ({
added: newTags.filter(tag => !oldTags.includes(tag)),
removed: oldTags.filter(tag => !newTags.includes(tag)),
});
/** Returns only tags which are badges. */
const filterBadges = (tags: string[]): string[] => tags.filter(tag => tag.startsWith('badge:'));