pl-fe: Avoid immutable
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
cc858d4f0f
commit
7e5a63c664
12 changed files with 103 additions and 76 deletions
|
@ -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<IAutosuggestAccountInput> = ({
|
|||
...rest
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [accountIds, setAccountIds] = useState(ImmutableOrderedSet<string>());
|
||||
const [accountIds, setAccountIds] = useState<Array<string>>([]);
|
||||
const controller = useRef(new AbortController());
|
||||
|
||||
const refreshCancelToken = () => {
|
||||
|
@ -39,14 +38,14 @@ const AutosuggestAccountInput: React.FC<IAutosuggestAccountInput> = ({
|
|||
};
|
||||
|
||||
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<IAutosuggestAccountInput> = ({
|
|||
<AutosuggestInput
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
suggestions={accountIds.toList()}
|
||||
suggestions={accountIds}
|
||||
onSuggestionsFetchRequested={noOp}
|
||||
onSuggestionsClearRequested={noOp}
|
||||
onSuggestionSelected={handleSelected}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import AutosuggestEmoji from 'pl-fe/components/autosuggest-emoji';
|
||||
|
@ -17,7 +16,7 @@ type AutoSuggestion = string | Emoji;
|
|||
|
||||
interface IAutosuggestInput extends Pick<React.HTMLAttributes<HTMLInputElement>, 'onChange' | 'onKeyUp' | 'onKeyDown'> {
|
||||
value: string;
|
||||
suggestions: ImmutableList<any>;
|
||||
suggestions: Array<any>;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
onSuggestionSelected: (tokenStart: number, lastToken: string | null, suggestion: AutoSuggestion) => void;
|
||||
|
@ -40,7 +39,7 @@ class AutosuggestInput extends PureComponent<IAutosuggestInput> {
|
|||
static defaultProps = {
|
||||
autoFocus: false,
|
||||
autoSelect: true,
|
||||
searchTokens: ImmutableList(['@', ':', '#']),
|
||||
searchTokens: ['@', ':', '#'],
|
||||
};
|
||||
|
||||
getFirstIndex = () => this.props.autoSelect ? 0 : -1;
|
||||
|
@ -79,7 +78,7 @@ class AutosuggestInput extends PureComponent<IAutosuggestInput> {
|
|||
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<IAutosuggestInput> {
|
|||
|
||||
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<IAutosuggestInput> {
|
|||
|
||||
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<IAutosuggestInput> {
|
|||
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<IAutosuggestInput> {
|
|||
|
||||
onSuggestionClick: React.EventHandler<React.MouseEvent | React.TouchEvent> = (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<IAutosuggestInput> {
|
|||
|
||||
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<IAutosuggestInput> {
|
|||
|
||||
return menu.map((item, i) => (
|
||||
<a
|
||||
className={clsx('flex cursor-pointer items-center space-x-2 px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800 dark:focus:bg-primary-800', { selected: suggestions.size - selectedSuggestion === i })}
|
||||
className={clsx('flex cursor-pointer items-center space-x-2 px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800 dark:focus:bg-primary-800', {
|
||||
selected: suggestions.length - selectedSuggestion === i,
|
||||
})}
|
||||
href='#'
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
|
@ -261,7 +262,7 @@ class AutosuggestInput extends PureComponent<IAutosuggestInput> {
|
|||
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 [
|
||||
<div key='input' className='relative w-full'>
|
||||
|
|
|
@ -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<ILocationSearch> = ({ onSelected }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const [locationIds, setLocationIds] = useState(ImmutableOrderedSet<string>());
|
||||
const [locationIds, setLocationIds] = useState<Array<string>>([]);
|
||||
const controller = useRef(new AbortController());
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
|
@ -63,14 +62,14 @@ const LocationSearch: React.FC<ILocationSearch> = ({ 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<ILocationSearch> = ({ onSelected }) => {
|
|||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
suggestions={locationIds.toList()}
|
||||
suggestions={locationIds}
|
||||
onSuggestionsFetchRequested={noOp}
|
||||
onSuggestionsClearRequested={noOp}
|
||||
onSuggestionSelected={handleSelected}
|
||||
|
|
|
@ -81,7 +81,7 @@ const AuthToken: React.FC<IAuthToken> = ({ 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);
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ const Option: React.FC<IOption> = ({
|
|||
maxLength={maxChars}
|
||||
value={title}
|
||||
onChange={handleOptionTitleChange}
|
||||
suggestions={suggestions}
|
||||
suggestions={suggestions.toArray()}
|
||||
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={onSuggestionsClearRequested}
|
||||
onSuggestionSelected={onSuggestionSelected}
|
||||
|
|
|
@ -37,7 +37,7 @@ const SpoilerInput = React.forwardRef<AutosuggestInput, ISpoilerInput>(({
|
|||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChangeSpoilerText}
|
||||
suggestions={suggestions}
|
||||
suggestions={suggestions.toArray()}
|
||||
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={onSuggestionsClearRequested}
|
||||
onSuggestionSelected={onSuggestionSelected}
|
||||
|
|
|
@ -26,7 +26,7 @@ const HashtagTimeline: React.FC<IHashtagTimeline> = ({ 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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<OauthToken>,
|
||||
interface State {
|
||||
tokens: Array<OauthToken>;
|
||||
mfa: {
|
||||
settings: Record<string, boolean>;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
tokens: [],
|
||||
mfa: {
|
||||
settings: {
|
||||
totp: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<string, Tag>();
|
||||
type State = Record<string, Tag>;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<string>(),
|
||||
isLoading: false,
|
||||
});
|
||||
interface State {
|
||||
items: Array<string>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
const initialState: State = {
|
||||
items: [],
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const toIds = (items: Array<Status>) => items.map(item => item.id);
|
||||
|
||||
const importStatuses = (state: State, statuses: Array<Status>) =>
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<Tag>(),
|
||||
interface State {
|
||||
items: Array<Tag>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
items: [],
|
||||
isLoading: false,
|
||||
});
|
||||
};
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue