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> </CardHeader>
<Search /> <Search />
{ {
loaded && searchAccountIds.size === 0 ? ( loaded && searchAccountIds.length === 0 ? (
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage id='empty_column.aliases.suggestions' defaultMessage='There are no account suggestions available for the provided term.' /> <FormattedMessage id='empty_column.aliases.suggestions' defaultMessage='There are no account suggestions available for the provided term.' />
</div> </div>

View file

@ -1,4 +1,3 @@
import { Map as ImmutableMap } from 'immutable';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
@ -19,7 +18,7 @@ interface ICaptchaField {
name?: string; name?: string;
value: string; value: string;
onChange?: React.ChangeEventHandler<HTMLInputElement>; onChange?: React.ChangeEventHandler<HTMLInputElement>;
onFetch?: (captcha: ImmutableMap<string, any>) => void; onFetch?: (captcha: Record<string, any>) => void;
onFetchFail?: (error: Error) => void; onFetchFail?: (error: Error) => void;
onClick?: React.MouseEventHandler; onClick?: React.MouseEventHandler;
refreshInterval?: number; refreshInterval?: number;
@ -38,12 +37,11 @@ const CaptchaField: React.FC<ICaptchaField> = ({
}) => { }) => {
const dispatch = useAppDispatch(); 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 [refresh, setRefresh] = useState<NodeJS.Timeout | undefined>(undefined);
const getCaptcha = () => { const getCaptcha = () => {
dispatch(fetchCaptcha()).then((response) => { dispatch(fetchCaptcha()).then((captcha) => {
const captcha = ImmutableMap(response);
setCaptcha(captcha); setCaptcha(captcha);
onFetch(captcha); onFetch(captcha);
}).catch((error: Error) => { }).catch((error: Error) => {
@ -98,7 +96,7 @@ const CaptchaField: React.FC<ICaptchaField> = ({
}; };
interface INativeCaptchaField { interface INativeCaptchaField {
captcha: ImmutableMap<string, any>; captcha: Record<string, any>;
onChange: React.ChangeEventHandler<HTMLInputElement>; onChange: React.ChangeEventHandler<HTMLInputElement>;
onClick: React.MouseEventHandler; onClick: React.MouseEventHandler;
name?: string; name?: string;
@ -111,7 +109,7 @@ const NativeCaptchaField: React.FC<INativeCaptchaField> = ({ captcha, onChange,
return ( return (
<Stack space={2}> <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'> <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> </div>
<Input <Input

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -31,8 +30,8 @@ const messages = defineMessages({
}); });
const getNotifications = createSelector([ const getNotifications = createSelector([
(state: RootState) => state.notifications.items.toList(), (state: RootState) => state.notifications.items.toArray(),
], (notifications) => notifications.filter(item => item !== null)); ], (notifications) => notifications.map(([_, notification]) => notification).filter(item => item !== null));
const Notifications = () => { const Notifications = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -47,7 +46,7 @@ const Notifications = () => {
const hasMore = useAppSelector(state => state.notifications.hasMore); const hasMore = useAppSelector(state => state.notifications.hasMore);
const totalQueuedNotificationsCount = useAppSelector(state => state.notifications.totalQueuedNotificationsCount || 0); 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) => { // const handleLoadGap = (maxId) => {
// dispatch(expandNotifications({ 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' 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." />; : <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 const filterBarContainer = showFilterBar
? (<FilterBar />) ? (<FilterBar />)
@ -113,7 +112,7 @@ const Notifications = () => {
if (isLoading && scrollableContentRef.current) { if (isLoading && scrollableContentRef.current) {
scrollableContent = scrollableContentRef.current; scrollableContent = scrollableContentRef.current;
} else if (notifications.size > 0 || hasMore) { } else if (notifications.length > 0 || hasMore) {
scrollableContent = notifications.map((item) => ( scrollableContent = notifications.map((item) => (
<Notification <Notification
key={item.group_key} key={item.group_key}
@ -131,7 +130,7 @@ const Notifications = () => {
const scrollContainer = ( const scrollContainer = (
<ScrollableList <ScrollableList
isLoading={isLoading} isLoading={isLoading}
showLoading={isLoading && notifications.size === 0} showLoading={isLoading && notifications.length === 0}
hasMore={hasMore} hasMore={hasMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
placeholderComponent={PlaceholderNotification} placeholderComponent={PlaceholderNotification}
@ -139,7 +138,7 @@ const Notifications = () => {
onLoadMore={handleLoadOlder} onLoadMore={handleLoadOlder}
onScroll={handleScroll} onScroll={handleScroll}
listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', { 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!} {scrollableContent!}

View file

@ -1,4 +1,3 @@
import { Record as ImmutableRecord } from 'immutable';
import { MediaAttachment } from 'pl-api'; import { MediaAttachment } from 'pl-api';
import React, { useState } from 'react'; import React, { useState } from 'react';
@ -7,12 +6,12 @@ interface IPlaceholderMediaGallery {
defaultWidth?: number; defaultWidth?: number;
} }
const SizeData = ImmutableRecord({ interface SizeData {
style: {} as React.CSSProperties, style: React.CSSProperties;
itemsDimensions: [] as Record<string, string>[], itemsDimensions: Record<string, string>[];
size: 1 as number, size: number;
width: 0 as number, width: number;
}); }
const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, defaultWidth }) => { const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, defaultWidth }) => {
const [width, setWidth] = useState(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 = {}; const style: React.CSSProperties = {};
let itemsDimensions: Record<string, string>[] = []; let itemsDimensions: Record<string, string>[] = [];
@ -59,12 +58,12 @@ const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, de
]; ];
} }
return SizeData({ return {
style, style,
itemsDimensions, itemsDimensions,
size, size,
width, width: width || 0,
}); };
}; };
const renderItem = (dimensions: Record<string, string>, i: number) => { 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 React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -106,7 +105,7 @@ const Preferences = () => {
}; };
const onSelectMultiple = (selectedList: string[], path: string[]) => { 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) => { const onToggleChange = (key: string[], checked: boolean) => {

View file

@ -54,7 +54,7 @@ const ListEditorModal: React.FC<BaseModalProps & ListEditorModalProps> = ({ list
<EditListForm /> <EditListForm />
<br /> <br />
{accountIds.size > 0 && ( {accountIds.length > 0 && (
<div> <div>
<CardHeader> <CardHeader>
<CardTitle title={intl.formatMessage(messages.removeFromList)} /> <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 { import {
ALIASES_SUGGESTIONS_READY, ALIASES_SUGGESTIONS_READY,
@ -8,37 +8,52 @@ import {
AliasesAction, AliasesAction,
} from '../actions/aliases'; } from '../actions/aliases';
const ReducerRecord = ImmutableRecord({ interface State {
aliases: ImmutableRecord({ aliases: {
items: ImmutableList<string>(), items: Array<string>;
loaded: boolean;
};
suggestions: {
items: Array<string>;
value: string;
loaded: boolean;
};
}
const initialState: State = {
aliases: {
items: [],
loaded: false, loaded: false,
})(), },
suggestions: ImmutableRecord({ suggestions: {
items: ImmutableList<string>(), items: [],
value: '', value: '',
loaded: false, loaded: false,
})(), },
}); };
const aliasesReducer = (state = ReducerRecord(), action: AliasesAction) => { const aliasesReducer = (state = initialState, action: AliasesAction): State => {
switch (action.type) { switch (action.type) {
case ALIASES_FETCH_SUCCESS: case ALIASES_FETCH_SUCCESS:
return state return create(state, (draft) => {
.setIn(['aliases', 'items'], action.value); draft.aliases.items = action.value;
});
case ALIASES_SUGGESTIONS_CHANGE: case ALIASES_SUGGESTIONS_CHANGE:
return state return create(state, (draft) => {
.setIn(['suggestions', 'value'], action.value) draft.suggestions.value = action.value;
.setIn(['suggestions', 'loaded'], false); draft.suggestions.loaded = false;
});
case ALIASES_SUGGESTIONS_READY: case ALIASES_SUGGESTIONS_READY:
return state return create(state, (draft) => {
.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item) => item.id))) draft.suggestions.items = action.accounts.map((item) => item.id);
.setIn(['suggestions', 'loaded'], true); draft.suggestions.loaded = true;
});
case ALIASES_SUGGESTIONS_CLEAR: case ALIASES_SUGGESTIONS_CLEAR:
return state.update('suggestions', suggestions => suggestions.withMutations(map => { return create(state, (draft) => {
map.set('items', ImmutableList()); draft.suggestions.items = [];
map.set('value', ''); draft.suggestions.value = '';
map.set('loaded', false); draft.suggestions.loaded = false;
})); });
default: default:
return state; return state;
} }

View file

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

View file

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

View file

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

View file

@ -1,5 +1,3 @@
import { List as ImmutableList } from 'immutable';
import { selectAccount, selectOwnAccount } from 'pl-fe/selectors'; import { selectAccount, selectOwnAccount } from 'pl-fe/selectors';
import type { RootState } from 'pl-fe/store'; import type { RootState } from 'pl-fe/store';
@ -43,19 +41,19 @@ const getAccessToken = (state: RootState) => {
const getAuthUserId = (state: RootState) => { const getAuthUserId = (state: RootState) => {
const me = state.auth.me; const me = state.auth.me;
return ImmutableList([ return [
state.auth.users.get(me!)?.id, state.auth.users.get(me!)?.id,
me, me,
].filter(id => id)).find(validId); ].filter(id => id).find(validId);
}; };
const getAuthUserUrl = (state: RootState) => { const getAuthUserUrl = (state: RootState) => {
const me = state.auth.me; const me = state.auth.me;
return ImmutableList([ return [
state.auth.users.get(me!)?.url, state.auth.users.get(me!)?.url,
me, me,
].filter(url => url)).find(isURL); ].filter(url => url).find(isURL);
}; };
/** Get the VAPID public key. */ /** 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'; import type { Account } from 'pl-fe/normalizers/account';
/** Convert a plain tag into a badge. */ /** Convert a plain tag into a badge. */
@ -17,15 +15,10 @@ interface TagDiff {
} }
/** Returns the differences between two sets of tags. */ /** Returns the differences between two sets of tags. */
const getTagDiff = (oldTags: string[], newTags: string[]): TagDiff => { const getTagDiff = (oldTags: string[], newTags: string[]): TagDiff => ({
const o = ImmutableOrderedSet(oldTags); added: newTags.filter(tag => !oldTags.includes(tag)),
const n = ImmutableOrderedSet(newTags); removed: oldTags.filter(tag => !newTags.includes(tag)),
});
return {
added: n.subtract(o).toArray(),
removed: o.subtract(n).toArray(),
};
};
/** Returns only tags which are badges. */ /** Returns only tags which are badges. */
const filterBadges = (tags: string[]): string[] => tags.filter(tag => tag.startsWith('badge:')); const filterBadges = (tags: string[]): string[] => tags.filter(tag => tag.startsWith('badge:'));