pl-fe: migrations off immutable

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-11-10 00:22:03 +01:00
parent 9f51735099
commit 7b405ef1d0
16 changed files with 81 additions and 74 deletions

View file

@ -59,7 +59,7 @@ const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL' as const;
const fetchList = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { const fetchList = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return; if (!isLoggedIn(getState)) return;
if (getState().lists.get(listId)) { if (getState().lists[listId]) {
return; return;
} }
@ -124,7 +124,7 @@ const submitListEditor = (shouldReset?: boolean) => (dispatch: AppDispatch, getS
const setupListEditor = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { const setupListEditor = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ dispatch({
type: LIST_EDITOR_SETUP, type: LIST_EDITOR_SETUP,
list: getState().lists.get(String(listId)), list: getState().lists[listId],
}); });
dispatch(fetchListAccounts(listId)); dispatch(fetchListAccounts(listId));

View file

@ -44,7 +44,7 @@ const unsubscribe = ({ registration, subscription }: {
const sendSubscriptionToBackend = (subscription: PushSubscription, me: Me) => const sendSubscriptionToBackend = (subscription: PushSubscription, me: Me) =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
const alerts = getState().push_notifications.alerts.toJS() as Record<string, boolean>; const alerts = getState().push_notifications.alerts;
const params = { subscription, data: { alerts } }; const params = { subscription, data: { alerts } };
if (me) { if (me) {
@ -138,7 +138,7 @@ const register = () =>
const saveSettings = () => const saveSettings = () =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState().push_notifications; const state = getState().push_notifications;
const alerts = state.alerts.toJS(); const alerts = state.alerts;
const data = { alerts }; const data = { alerts };
const me = getState().me; const me = getState().me;

View file

@ -98,7 +98,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
const { settings } = useSettingsStore(); const { settings } = useSettingsStore();
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
const interactionRequestsCount = useInteractionRequestsCount().data || 0; const interactionRequestsCount = useInteractionRequestsCount().data || 0;
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); const scheduledStatusCount = useAppSelector((state) => Object.keys(state.scheduled_statuses).length);
const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length); const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length);
// const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); // const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen); const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen);

View file

@ -51,7 +51,7 @@ const SidebarNavigation = () => {
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
const interactionRequestsCount = useInteractionRequestsCount().data || 0; const interactionRequestsCount = useInteractionRequestsCount().data || 0;
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length); const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); const scheduledStatusCount = useAppSelector((state) => Object.keys(state.scheduled_statuses).length);
const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length); const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length);
const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated; const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated;

View file

@ -78,10 +78,10 @@ const getItems = (features: Features, lists: ReturnType<typeof getOrderedLists>,
text: intl.formatMessage(messages.local_short), text: intl.formatMessage(messages.local_short),
meta: intl.formatMessage(messages.local_long), meta: intl.formatMessage(messages.local_long),
} : undefined, } : undefined,
features.addressableLists && !lists.isEmpty() ? { features.addressableLists && Object.keys(lists).length ? {
icon: require('@tabler/icons/outline/list.svg'), icon: require('@tabler/icons/outline/list.svg'),
value: '', value: '',
items: lists.toArray().map((list) => ({ items: Object.values(lists).map((list) => ({
icon: require('@tabler/icons/outline/list.svg'), icon: require('@tabler/icons/outline/list.svg'),
value: `list:${list.id}`, value: `list:${list.id}`,
text: list.title, text: list.title,

View file

@ -34,7 +34,7 @@ const ListTimeline: React.FC = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { openModal } = useModalsStore(); const { openModal } = useModalsStore();
const list = useAppSelector((state) => state.lists.get(id)); const list = useAppSelector((state) => state.lists[id]);
useListStream(id); useListStream(id);

View file

@ -28,7 +28,7 @@ const getOrderedLists = createSelector([(state: RootState) => state.lists], list
return lists; return lists;
} }
return lists.toList().filter((item): item is ListEntity => !!item).sort((a, b) => a.title.localeCompare(b.title)); return Object.values(lists).filter((item): item is ListEntity => !!item).sort((a, b) => a.title.localeCompare(b.title));
}); });
const Lists: React.FC = () => { const Lists: React.FC = () => {
@ -56,7 +56,7 @@ const Lists: React.FC = () => {
<Stack space={4}> <Stack space={4}>
<NewListForm /> <NewListForm />
{lists.isEmpty() ? ( {!Object.keys(lists).length ? (
<Card variant='rounded' size='lg'> <Card variant='rounded' size='lg'>
{emptyMessage} {emptyMessage}
</Card> </Card>

View file

@ -20,7 +20,7 @@ interface IScheduledStatus {
const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) => { const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) => {
const status = useAppSelector((state) => { const status = useAppSelector((state) => {
const scheduledStatus = state.scheduled_statuses.get(statusId); const scheduledStatus = state.scheduled_statuses[statusId];
if (!scheduledStatus) return null; if (!scheduledStatus) return null;
return buildStatus(state, scheduledStatus); return buildStatus(state, scheduledStatus);
}); });

View file

@ -20,7 +20,7 @@ const List: React.FC<IList> = ({ listId }) => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const list = useAppSelector((state) => state.lists.get(listId)); const list = useAppSelector((state) => state.lists[listId]);
const added = useAppSelector((state) => state.listAdder.lists.items.includes(listId)); const added = useAppSelector((state) => state.listAdder.lists.items.includes(listId));
const onRemove = () => dispatch(removeFromListAdder(listId)); const onRemove = () => dispatch(removeFromListAdder(listId));

View file

@ -61,7 +61,7 @@ const ListAdderModal: React.FC<BaseModalProps & ListAdderModalProps> = ({ accoun
<CardTitle title={intl.formatMessage(messages.subheading)} /> <CardTitle title={intl.formatMessage(messages.subheading)} />
</CardHeader> </CardHeader>
<div> <div>
{listIds.map(ListId => <List key={ListId} listId={ListId} />)} {listIds.map(listId => <List key={listId} listId={listId} />)}
</div> </div>
</Modal> </Modal>
); );

View file

@ -1,18 +1,14 @@
import { List as ImmutableList } from 'immutable';
import { FILTERS_FETCH_SUCCESS } from '../actions/filters'; import { FILTERS_FETCH_SUCCESS } from '../actions/filters';
import type { Filter } from 'pl-api'; import type { Filter } from 'pl-api';
import type { AnyAction } from 'redux'; import type { AnyAction } from 'redux';
type State = ImmutableList<Filter>; type State = Array<Filter>;
const importFilters = (_state: State, filters: Array<Filter>): State => ImmutableList(filters); const filters = (state: State = [], action: AnyAction): State => {
const filters = (state: State = ImmutableList(), action: AnyAction): State => {
switch (action.type) { switch (action.type) {
case FILTERS_FETCH_SUCCESS: case FILTERS_FETCH_SUCCESS:
return importFilters(state, action.filters); return action.filters;
default: default:
return state; return state;
} }

View file

@ -1,4 +1,3 @@
import { Map as ImmutableMap } from 'immutable';
import { create } from 'mutative'; import { create } from 'mutative';
import { type Instance, instanceSchema, PleromaConfig } from 'pl-api'; import { type Instance, instanceSchema, PleromaConfig } from 'pl-api';
import * as v from 'valibot'; import * as v from 'valibot';
@ -20,11 +19,11 @@ const preloadImport = (state: State, action: Record<string, any>, path: string)
return instance ? v.parse(instanceSchema, instance) : state; return instance ? v.parse(instanceSchema, instance) : state;
}; };
const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string) => { const getConfigValue = (instanceConfig: Array<any>, key: string) => {
const v = instanceConfig const v = instanceConfig
.find(value => value.getIn(['tuple', 0]) === key); .find(value => value?.tuple?.[0] === key);
return v ? v.getIn(['tuple', 1]) : undefined; return v ? v?.tuple?.[1] : undefined;
}; };
const importConfigs = (state: State, configs: PleromaConfig['configs']) => { const importConfigs = (state: State, configs: PleromaConfig['configs']) => {

View file

@ -1,4 +1,4 @@
import { Map as ImmutableMap } from 'immutable'; import { create } from 'mutative';
import { import {
LIST_FETCH_SUCCESS, LIST_FETCH_SUCCESS,
@ -12,18 +12,16 @@ import {
import type { List } from 'pl-api'; import type { List } from 'pl-api';
import type { AnyAction } from 'redux'; import type { AnyAction } from 'redux';
type State = ImmutableMap<string, List | false>; type State = Record<string, List | false>;
const initialState: State = ImmutableMap(); const initialState: State = {};
const importList = (state: State, list: List) => state.set(list.id, list); const importList = (state: State, list: List) => {
state[list.id] = list;
};
const importLists = (state: State, lists: Array<List>) => { const importLists = (state: State, lists: Array<List>) => {
lists.forEach(list => { lists.forEach(list => importList(state, list));
state = importList(state, list);
});
return state;
}; };
const lists = (state: State = initialState, action: AnyAction) => { const lists = (state: State = initialState, action: AnyAction) => {
@ -31,12 +29,18 @@ const lists = (state: State = initialState, action: AnyAction) => {
case LIST_FETCH_SUCCESS: case LIST_FETCH_SUCCESS:
case LIST_CREATE_SUCCESS: case LIST_CREATE_SUCCESS:
case LIST_UPDATE_SUCCESS: case LIST_UPDATE_SUCCESS:
return importList(state, action.list); return create(state, (draft) => {
importList(draft, action.list);
});
case LISTS_FETCH_SUCCESS: case LISTS_FETCH_SUCCESS:
return importLists(state, action.lists); return create(state, (draft) => {
importLists(draft, action.lists);
});
case LIST_DELETE_SUCCESS: case LIST_DELETE_SUCCESS:
case LIST_FETCH_FAIL: case LIST_FETCH_FAIL:
return state.set(action.listId, false); return create(state, (draft) => {
draft[action.listId] = false;
});
default: default:
return state; return state;
} }

View file

@ -1,44 +1,52 @@
import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable'; import { create } from 'mutative';
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION } from '../actions/push-notifications'; import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION } from '../actions/push-notifications';
import type { SetterAction } from 'pl-fe/actions/push-notifications/setter'; import type { SetterAction } from 'pl-fe/actions/push-notifications/setter';
const SubscriptionRecord = ImmutableRecord({ interface Subscription {
id: '', id: string;
endpoint: '', endpoint: string;
}); }
const ReducerRecord = ImmutableRecord({ interface State {
subscription: null as Subscription | null, subscription: Subscription | null;
alerts: ImmutableMap<string, boolean>({ alerts: Record<string, boolean>;
isSubscribed: boolean;
browserSupport: boolean;
}
const initialState: State = {
subscription: null,
alerts: {
follow: true, follow: true,
follow_request: true, follow_request: true,
favourite: true, favourite: true,
reblog: true, reblog: true,
mention: true, mention: true,
poll: true, poll: true,
}), },
isSubscribed: false, isSubscribed: false,
browserSupport: false, browserSupport: false,
}); };
type Subscription = ReturnType<typeof SubscriptionRecord>; const push_subscriptions = (state = initialState, action: SetterAction): State => {
const push_subscriptions = (state = ReducerRecord(), action: SetterAction) => {
switch (action.type) { switch (action.type) {
case SET_SUBSCRIPTION: case SET_SUBSCRIPTION:
return state return create(state, (draft) => {
.set('subscription', SubscriptionRecord({ draft.subscription = {
id: action.subscription.id, id: action.subscription.id,
endpoint: action.subscription.endpoint, endpoint: action.subscription.endpoint,
})) };
.set('alerts', ImmutableMap(action.subscription.alerts)) draft.alerts = action.subscription.alerts;
.set('isSubscribed', true); draft.isSubscribed = true;
});
case SET_BROWSER_SUPPORT: case SET_BROWSER_SUPPORT:
return state.set('browserSupport', action.value); return create(state, (draft) => {
draft.browserSupport = action.value;
});
case CLEAR_SUBSCRIPTION: case CLEAR_SUBSCRIPTION:
return ReducerRecord(); return initialState;
default: default:
return state; return state;
} }

View file

@ -1,4 +1,4 @@
import { Map as ImmutableMap } from 'immutable'; import { create } from 'mutative';
import { STATUS_IMPORT, STATUSES_IMPORT, type ImporterAction } from 'pl-fe/actions/importer'; import { STATUS_IMPORT, STATUSES_IMPORT, type ImporterAction } from 'pl-fe/actions/importer';
import { import {
@ -11,31 +11,34 @@ import { STATUS_CREATE_SUCCESS } from 'pl-fe/actions/statuses';
import type { Status, ScheduledStatus } from 'pl-api'; import type { Status, ScheduledStatus } from 'pl-api';
import type { AnyAction } from 'redux'; import type { AnyAction } from 'redux';
type State = ImmutableMap<string, ScheduledStatus>; type State = Record<string, ScheduledStatus>;
const initialState: State = ImmutableMap(); const initialState: State = {};
const importStatus = (state: State, status: Status | ScheduledStatus) => { const importStatus = (state: State, status: Status | ScheduledStatus) => {
if (!status.scheduled_at) return state; if (!status.scheduled_at) return state;
return state.set(status.id, status); state[status.id] = status;
}; };
const importStatuses = (state: State, statuses: Array<Status | ScheduledStatus>) => const importStatuses = (state: State, statuses: Array<Status | ScheduledStatus>) => {
state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status))); statuses.forEach(status => importStatus(state, status));
};
const deleteStatus = (state: State, statusId: string) => state.delete(statusId); const deleteStatus = (state: State, statusId: string) => {
delete state[statusId];
};
const scheduled_statuses = (state: State = initialState, action: AnyAction | ImporterAction) => { const scheduled_statuses = (state: State = initialState, action: AnyAction | ImporterAction) => {
switch (action.type) { switch (action.type) {
case STATUS_IMPORT: case STATUS_IMPORT:
case STATUS_CREATE_SUCCESS: case STATUS_CREATE_SUCCESS:
return importStatus(state, action.status); return create(state, (draft) => importStatus(draft, action.status));
case STATUSES_IMPORT: case STATUSES_IMPORT:
case SCHEDULED_STATUSES_FETCH_SUCCESS: case SCHEDULED_STATUSES_FETCH_SUCCESS:
return importStatuses(state, action.statuses); return create(state, (draft) => importStatuses(draft, action.statuses));
case SCHEDULED_STATUS_CANCEL_REQUEST: case SCHEDULED_STATUS_CANCEL_REQUEST:
case SCHEDULED_STATUS_CANCEL_SUCCESS: case SCHEDULED_STATUS_CANCEL_SUCCESS:
return deleteStatus(state, action.statusId); return create(state, (draft) => deleteStatus(draft, action.statusId));
default: default:
return state; return state;
} }

View file

@ -1,7 +1,4 @@
import { import { OrderedSet as ImmutableOrderedSet } from 'immutable';
List as ImmutableList,
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';
@ -80,8 +77,8 @@ const getFilters = (state: RootState, query: FilterContext) =>
const escapeRegExp = (string: string) => const escapeRegExp = (string: string) =>
string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
const regexFromFilters = (filters: ImmutableList<Filter>) => { const regexFromFilters = (filters: Array<Filter>) => {
if (filters.size === 0) return null; if (filters.length === 0) return null;
return new RegExp(filters.map(filter => return new RegExp(filters.map(filter =>
filter.keywords.map(keyword => { filter.keywords.map(keyword => {
@ -102,7 +99,7 @@ const regexFromFilters = (filters: ImmutableList<Filter>) => {
).join('|'), 'i'); ).join('|'), 'i');
}; };
const checkFiltered = (index: string, filters: ImmutableList<Filter>) => const checkFiltered = (index: string, filters: Array<Filter>) =>
filters.reduce((result: Array<string>, filter) => filters.reduce((result: Array<string>, filter) =>
result.concat(filter.keywords.reduce((result: Array<string>, keyword) => { result.concat(filter.keywords.reduce((result: Array<string>, keyword) => {
let expr = escapeRegExp(keyword.keyword); let expr = escapeRegExp(keyword.keyword);