diff --git a/packages/pl-fe/src/components/autosuggest-account-input.tsx b/packages/pl-fe/src/components/autosuggest-account-input.tsx index ed166d14e..55e421ea8 100644 --- a/packages/pl-fe/src/components/autosuggest-account-input.tsx +++ b/packages/pl-fe/src/components/autosuggest-account-input.tsx @@ -1,4 +1,3 @@ -import { OrderedSet as ImmutableOrderedSet } from 'immutable'; import throttle from 'lodash/throttle'; import React, { useState, useRef, useCallback, useEffect } from 'react'; @@ -30,7 +29,7 @@ const AutosuggestAccountInput: React.FC = ({ ...rest }) => { const dispatch = useAppDispatch(); - const [accountIds, setAccountIds] = useState(ImmutableOrderedSet()); + const [accountIds, setAccountIds] = useState>([]); const controller = useRef(new AbortController()); const refreshCancelToken = () => { @@ -39,14 +38,14 @@ const AutosuggestAccountInput: React.FC = ({ }; const clearResults = () => { - setAccountIds(ImmutableOrderedSet()); + setAccountIds([]); }; const handleAccountSearch = useCallback(throttle((q) => { dispatch(accountSearch(q, controller.current.signal)) .then((accounts: { id: string }[]) => { const accountIds = accounts.map(account => account.id); - setAccountIds(ImmutableOrderedSet(accountIds)); + setAccountIds(accountIds); }) .catch(noOp); }, 900, { leading: true, trailing: true }), []); @@ -79,7 +78,7 @@ const AutosuggestAccountInput: React.FC = ({ , 'onChange' | 'onKeyUp' | 'onKeyDown'> { value: string; - suggestions: ImmutableList; + suggestions: Array; disabled?: boolean; placeholder?: string; onSuggestionSelected: (tokenStart: number, lastToken: string | null, suggestion: AutoSuggestion) => void; @@ -40,7 +39,7 @@ class AutosuggestInput extends PureComponent { static defaultProps = { autoFocus: false, autoSelect: true, - searchTokens: ImmutableList(['@', ':', '#']), + searchTokens: ['@', ':', '#'], }; getFirstIndex = () => this.props.autoSelect ? 0 : -1; @@ -79,7 +78,7 @@ class AutosuggestInput extends PureComponent { const { suggestions, menu, disabled } = this.props; const { selectedSuggestion, suggestionsHidden } = this.state; const firstIndex = this.getFirstIndex(); - const lastIndex = suggestions.size + (menu || []).length - 1; + const lastIndex = suggestions.length + (menu || []).length - 1; if (disabled) { e.preventDefault(); @@ -94,7 +93,7 @@ class AutosuggestInput extends PureComponent { switch (e.key) { case 'Escape': - if (suggestions.size === 0 || suggestionsHidden) { + if (suggestions.length === 0 || suggestionsHidden) { document.querySelector('.ui')?.parentElement?.focus(); } else { e.preventDefault(); @@ -103,14 +102,14 @@ class AutosuggestInput extends PureComponent { break; case 'ArrowDown': - if (!suggestionsHidden && (suggestions.size > 0 || menu)) { + if (!suggestionsHidden && (suggestions.length > 0 || menu)) { e.preventDefault(); this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, lastIndex) }); } break; case 'ArrowUp': - if (!suggestionsHidden && (suggestions.size > 0 || menu)) { + if (!suggestionsHidden && (suggestions.length > 0 || menu)) { e.preventDefault(); this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, firstIndex) }); } @@ -119,15 +118,15 @@ class AutosuggestInput extends PureComponent { case 'Enter': case 'Tab': // Select suggestion - if (!suggestionsHidden && selectedSuggestion > -1 && (suggestions.size > 0 || menu)) { + if (!suggestionsHidden && selectedSuggestion > -1 && (suggestions.length > 0 || menu)) { e.preventDefault(); e.stopPropagation(); this.setState({ selectedSuggestion: firstIndex }); - if (selectedSuggestion < suggestions.size) { - this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); + if (selectedSuggestion < suggestions.length) { + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions[selectedSuggestion]); } else if (menu) { - const item = menu[selectedSuggestion - suggestions.size]; + const item = menu[selectedSuggestion - suggestions.length]; this.handleMenuItemAction(item, e); } } @@ -154,7 +153,7 @@ class AutosuggestInput extends PureComponent { onSuggestionClick: React.EventHandler = (e) => { const index = Number(e.currentTarget?.getAttribute('data-index')); - const suggestion = this.props.suggestions.get(index); + const suggestion = this.props.suggestions[index]; this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); this.input?.focus(); e.preventDefault(); @@ -162,7 +161,7 @@ class AutosuggestInput extends PureComponent { componentDidUpdate(prevProps: IAutosuggestInput, prevState: any) { const { suggestions } = this.props; - if (suggestions !== prevProps.suggestions && suggestions.size > 0 && prevState.suggestionsHidden && prevState.focused) { + if (suggestions !== prevProps.suggestions && suggestions.length > 0 && prevState.suggestionsHidden && prevState.focused) { this.setState({ suggestionsHidden: false }); } } @@ -231,7 +230,9 @@ class AutosuggestInput extends PureComponent { return menu.map((item, i) => ( { const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, menu, theme } = this.props; const { suggestionsHidden } = this.state; - const visible = !suggestionsHidden && (!suggestions.isEmpty() || (menu && value)); + const visible = !suggestionsHidden && (suggestions.length || (menu && value)); return [
diff --git a/packages/pl-fe/src/components/location-search.tsx b/packages/pl-fe/src/components/location-search.tsx index 444c7c25c..0716b1a96 100644 --- a/packages/pl-fe/src/components/location-search.tsx +++ b/packages/pl-fe/src/components/location-search.tsx @@ -1,5 +1,4 @@ import clsx from 'clsx'; -import { OrderedSet as ImmutableOrderedSet } from 'immutable'; import throttle from 'lodash/throttle'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -24,7 +23,7 @@ interface ILocationSearch { const LocationSearch: React.FC = ({ onSelected }) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const [locationIds, setLocationIds] = useState(ImmutableOrderedSet()); + const [locationIds, setLocationIds] = useState>([]); const controller = useRef(new AbortController()); const [value, setValue] = useState(''); @@ -63,14 +62,14 @@ const LocationSearch: React.FC = ({ onSelected }) => { }; const clearResults = () => { - setLocationIds(ImmutableOrderedSet()); + setLocationIds([]); }; const handleLocationSearch = useCallback(throttle(q => { dispatch(locationSearch(q, controller.current.signal)) .then((locations: { origin_id: string }[]) => { const locationIds = locations.map(location => location.origin_id); - setLocationIds(ImmutableOrderedSet(locationIds)); + setLocationIds(locationIds); }) .catch(noOp); }, 900, { leading: true, trailing: true }), []); @@ -88,7 +87,7 @@ const LocationSearch: React.FC = ({ onSelected }) => { placeholder={intl.formatMessage(messages.placeholder)} value={value} onChange={handleChange} - suggestions={locationIds.toList()} + suggestions={locationIds} onSuggestionsFetchRequested={noOp} onSuggestionsClearRequested={noOp} onSuggestionSelected={handleSelected} diff --git a/packages/pl-fe/src/features/auth-token-list/index.tsx b/packages/pl-fe/src/features/auth-token-list/index.tsx index f92296681..fab77ed6d 100644 --- a/packages/pl-fe/src/features/auth-token-list/index.tsx +++ b/packages/pl-fe/src/features/auth-token-list/index.tsx @@ -81,7 +81,7 @@ const AuthToken: React.FC = ({ token, isCurrent }) => { const AuthTokenList: React.FC = () => { const dispatch = useAppDispatch(); const intl = useIntl(); - const tokens = useAppSelector(state => state.security.get('tokens').toReversed()); + const tokens = useAppSelector(state => state.security.tokens.toReversed()); const currentTokenId = useAppSelector(state => { const currentToken = state.auth.tokens.valueSeq().find((token) => token.me === state.auth.me); diff --git a/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx b/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx index 31e6df6ba..81189fd9b 100644 --- a/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx +++ b/packages/pl-fe/src/features/compose/components/polls/poll-form.tsx @@ -90,7 +90,7 @@ const Option: React.FC = ({ maxLength={maxChars} value={title} onChange={handleOptionTitleChange} - suggestions={suggestions} + suggestions={suggestions.toArray()} onSuggestionsFetchRequested={onSuggestionsFetchRequested} onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={onSuggestionSelected} diff --git a/packages/pl-fe/src/features/compose/components/spoiler-input.tsx b/packages/pl-fe/src/features/compose/components/spoiler-input.tsx index 2fee60676..4c64e20e7 100644 --- a/packages/pl-fe/src/features/compose/components/spoiler-input.tsx +++ b/packages/pl-fe/src/features/compose/components/spoiler-input.tsx @@ -37,7 +37,7 @@ const SpoilerInput = React.forwardRef(({ placeholder={intl.formatMessage(messages.placeholder)} value={value} onChange={handleChangeSpoilerText} - suggestions={suggestions} + suggestions={suggestions.toArray()} onSuggestionsFetchRequested={onSuggestionsFetchRequested} onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={onSuggestionSelected} diff --git a/packages/pl-fe/src/features/hashtag-timeline/index.tsx b/packages/pl-fe/src/features/hashtag-timeline/index.tsx index 4b39f2e69..78596c772 100644 --- a/packages/pl-fe/src/features/hashtag-timeline/index.tsx +++ b/packages/pl-fe/src/features/hashtag-timeline/index.tsx @@ -26,7 +26,7 @@ const HashtagTimeline: React.FC = ({ params }) => { const features = useFeatures(); const dispatch = useAppDispatch(); - const tag = useAppSelector((state) => state.tags.get(tagId)); + const tag = useAppSelector((state) => state.tags[tagId]); const { isLoggedIn } = useLoggedIn(); const theme = useTheme(); const isMobile = useIsMobile(); diff --git a/packages/pl-fe/src/features/settings/index.tsx b/packages/pl-fe/src/features/settings/index.tsx index 965a394f7..7c0d84cdb 100644 --- a/packages/pl-fe/src/features/settings/index.tsx +++ b/packages/pl-fe/src/features/settings/index.tsx @@ -49,7 +49,7 @@ const Settings = () => { const dispatch = useAppDispatch(); const intl = useIntl(); - const mfa = useAppSelector((state) => state.security.get('mfa')); + const mfa = useAppSelector((state) => state.security.mfa); const features = useFeatures(); const { account } = useOwnAccount(); diff --git a/packages/pl-fe/src/reducers/security.ts b/packages/pl-fe/src/reducers/security.ts index ef5a691f1..634e3f0dd 100644 --- a/packages/pl-fe/src/reducers/security.ts +++ b/packages/pl-fe/src/reducers/security.ts @@ -1,4 +1,4 @@ -import { Record as ImmutableRecord } from 'immutable'; +import { create } from 'mutative'; import { MFA_FETCH_SUCCESS, @@ -10,37 +10,52 @@ import { FETCH_TOKENS_SUCCESS, REVOKE_TOKEN_SUCCESS } from '../actions/security' import type { OauthToken } from 'pl-api'; import type { AnyAction } from 'redux'; -const ReducerRecord = ImmutableRecord({ - tokens: [] as Array, +interface State { + tokens: Array; + mfa: { + settings: Record; + }; +} + +const initialState: State = { + tokens: [], mfa: { settings: { totp: false, }, }, -}); +}; -type State = ReturnType; +const deleteToken = (state: State, tokenId: number) => state.tokens = state.tokens.filter(token => token.id !== tokenId); -const deleteToken = (state: State, tokenId: number) => state.update('tokens', tokens => tokens.filter(token => token.id !== tokenId)); +const importMfa = (state: State, data: any) => state.mfa = data; -const importMfa = (state: State, data: any) => state.set('mfa', data); +const enableMfa = (state: State, method: string) => state.mfa.settings = { ...state.mfa.settings, [method]: true }; -const enableMfa = (state: State, method: string) => state.update('mfa', mfa => ({ settings: { ...mfa.settings, [method]: true } })); +const disableMfa = (state: State, method: string) => state.mfa.settings = { ...state.mfa.settings, [method]: false }; -const disableMfa = (state: State, method: string) => state.update('mfa', mfa => ({ settings: { ...mfa.settings, [method]: false } })); - -const security = (state = ReducerRecord(), action: AnyAction) => { +const security = (state = initialState, action: AnyAction) => { switch (action.type) { case FETCH_TOKENS_SUCCESS: - return state.set('tokens', action.tokens); + return create(state, (draft) => { + draft.tokens = action.tokens; + }); case REVOKE_TOKEN_SUCCESS: - return deleteToken(state, action.tokenId); + return create(state, (draft) => { + deleteToken(draft, action.tokenId); + }); case MFA_FETCH_SUCCESS: - return importMfa(state, action.data); + return create(state, (draft) => { + importMfa(draft, action.data); + }); case MFA_CONFIRM_SUCCESS: - return enableMfa(state, action.method); + return create(state, (draft) => { + enableMfa(draft, action.method); + }); case MFA_DISABLE_SUCCESS: - return disableMfa(state, action.method); + return create(state, (draft) => { + disableMfa(draft, action.method); + }); default: return state; } diff --git a/packages/pl-fe/src/reducers/tags.ts b/packages/pl-fe/src/reducers/tags.ts index 7675d69c9..f75c92a6e 100644 --- a/packages/pl-fe/src/reducers/tags.ts +++ b/packages/pl-fe/src/reducers/tags.ts @@ -1,4 +1,4 @@ -import { Map as ImmutableMap } from 'immutable'; +import { create } from 'mutative'; import { HASHTAG_FETCH_SUCCESS, @@ -11,18 +11,26 @@ import { import type { Tag } from 'pl-api'; -const initialState = ImmutableMap(); +type State = Record; + +const initialState: State = {}; const tags = (state = initialState, action: TagsAction) => { switch (action.type) { case HASHTAG_FETCH_SUCCESS: - return state.set(action.name, action.tag); + return create(state, (draft) => { + draft[action.name] = action.tag; + }); case HASHTAG_FOLLOW_REQUEST: case HASHTAG_UNFOLLOW_FAIL: - return state.setIn([action.name, 'following'], true); + return create(state, (draft) => { + if (draft[action.name]) draft[action.name].following = true; + }); case HASHTAG_FOLLOW_FAIL: case HASHTAG_UNFOLLOW_REQUEST: - return state.setIn([action.name, 'following'], false); + return create(state, (draft) => { + if (draft[action.name]) draft[action.name].following = false; + }); default: return state; } diff --git a/packages/pl-fe/src/reducers/trending-statuses.ts b/packages/pl-fe/src/reducers/trending-statuses.ts index 66f44d674..1d2e04034 100644 --- a/packages/pl-fe/src/reducers/trending-statuses.ts +++ b/packages/pl-fe/src/reducers/trending-statuses.ts @@ -1,30 +1,32 @@ -import { Record as ImmutableRecord } from 'immutable'; +import { create } from 'mutative'; import { TRENDING_STATUSES_FETCH_REQUEST, TRENDING_STATUSES_FETCH_SUCCESS, type TrendingStatusesAction } from 'pl-fe/actions/trending-statuses'; import type { Status } from 'pl-api'; -const ReducerRecord = ImmutableRecord({ - items: Array(), - isLoading: false, -}); +interface State { + items: Array; + isLoading: boolean; +} -type State = ReturnType; +const initialState: State = { + items: [], + isLoading: false, +}; const toIds = (items: Array) => items.map(item => item.id); -const importStatuses = (state: State, statuses: Array) => - state.withMutations(state => { - state.set('items', toIds(statuses)); - state.set('isLoading', false); - }); - -const trending_statuses = (state: State = ReducerRecord(), action: TrendingStatusesAction) => { +const trending_statuses = (state = initialState, action: TrendingStatusesAction) => { switch (action.type) { case TRENDING_STATUSES_FETCH_REQUEST: - return state.set('isLoading', true); + return create(state, (draft) => { + draft.isLoading = true; + }); case TRENDING_STATUSES_FETCH_SUCCESS: - return importStatuses(state, action.statuses); + return create(state, (draft) => { + draft.items = toIds(action.statuses); + draft.isLoading = false; + }); default: return state; } diff --git a/packages/pl-fe/src/reducers/trends.ts b/packages/pl-fe/src/reducers/trends.ts index 98dfb7d1e..918602572 100644 --- a/packages/pl-fe/src/reducers/trends.ts +++ b/packages/pl-fe/src/reducers/trends.ts @@ -1,22 +1,25 @@ -import { Record as ImmutableRecord } from 'immutable'; +import { create } from 'mutative'; import { TRENDS_FETCH_SUCCESS, type TrendsAction } from '../actions/trends'; import type { Tag } from 'pl-api'; -const ReducerRecord = ImmutableRecord({ - items: Array(), +interface State { + items: Array; + isLoading: boolean; +} + +const initialState: State = { + items: [], isLoading: false, -}); +}; -type State = ReturnType; - -const trendsReducer = (state: State = ReducerRecord(), action: TrendsAction) => { +const trendsReducer = (state = initialState, action: TrendsAction) => { switch (action.type) { case TRENDS_FETCH_SUCCESS: - return state.withMutations(map => { - map.set('items', action.tags); - map.set('isLoading', false); + return create(state, (draft) => { + draft.items = action.tags; + draft.isLoading = false; }); default: return state;