pl-fe: Remove notification queue from reducer
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
dca8f15279
commit
74d0d4c60b
3 changed files with 45 additions and 101 deletions
|
@ -24,7 +24,6 @@ import type { AppDispatch, RootState } from 'pl-fe/store';
|
|||
const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const;
|
||||
const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP' as const;
|
||||
const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE' as const;
|
||||
const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE' as const;
|
||||
|
||||
const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST' as const;
|
||||
const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS' as const;
|
||||
|
@ -100,8 +99,6 @@ const updateNotificationsQueue = (notification: BaseNotification, intlMessages:
|
|||
|
||||
let filtered: boolean | null = false;
|
||||
|
||||
const isOnNotificationsPage = curPath === '/notifications';
|
||||
|
||||
if (notification.type === 'mention' || notification.type === 'status') {
|
||||
const regex = regexFromFilters(filters);
|
||||
const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content);
|
||||
|
@ -139,37 +136,7 @@ const updateNotificationsQueue = (notification: BaseNotification, intlMessages:
|
|||
});
|
||||
}
|
||||
|
||||
if (isOnNotificationsPage) {
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_UPDATE_QUEUE,
|
||||
notification,
|
||||
intlMessages,
|
||||
intlLocale,
|
||||
});
|
||||
} else {
|
||||
dispatch(updateNotifications(notification));
|
||||
}
|
||||
};
|
||||
|
||||
const dequeueNotifications = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const queuedNotifications = getState().notifications.queuedNotifications;
|
||||
const totalQueuedNotificationsCount = getState().notifications.totalQueuedNotificationsCount;
|
||||
|
||||
if (totalQueuedNotificationsCount === 0) {
|
||||
return;
|
||||
} else if (totalQueuedNotificationsCount > 0 && totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
||||
queuedNotifications.forEach((block) => {
|
||||
dispatch(updateNotifications(block.notification));
|
||||
});
|
||||
} else {
|
||||
dispatch(expandNotifications());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_DEQUEUE,
|
||||
});
|
||||
dispatch(markReadNotifications());
|
||||
dispatch(updateNotifications(notification));
|
||||
};
|
||||
|
||||
const excludeTypesFromFilter = (filters: string[]) => NOTIFICATION_TYPES.filter(item => !filters.includes(item));
|
||||
|
@ -295,7 +262,6 @@ export {
|
|||
NOTIFICATIONS_UPDATE,
|
||||
NOTIFICATIONS_UPDATE_NOOP,
|
||||
NOTIFICATIONS_UPDATE_QUEUE,
|
||||
NOTIFICATIONS_DEQUEUE,
|
||||
NOTIFICATIONS_EXPAND_REQUEST,
|
||||
NOTIFICATIONS_EXPAND_SUCCESS,
|
||||
NOTIFICATIONS_EXPAND_FAIL,
|
||||
|
@ -309,7 +275,6 @@ export {
|
|||
type FilterType,
|
||||
updateNotifications,
|
||||
updateNotificationsQueue,
|
||||
dequeueNotifications,
|
||||
expandNotifications,
|
||||
expandNotificationsRequest,
|
||||
expandNotificationsSuccess,
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import clsx from 'clsx';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import {
|
||||
expandNotifications,
|
||||
markReadNotifications,
|
||||
scrollTopNotifications,
|
||||
dequeueNotifications,
|
||||
} from 'pl-fe/actions/notifications';
|
||||
import PullToRefresh from 'pl-fe/components/pull-to-refresh';
|
||||
import ScrollTopButton from 'pl-fe/components/scroll-top-button';
|
||||
|
@ -31,7 +31,27 @@ const messages = defineMessages({
|
|||
|
||||
const getNotifications = createSelector([
|
||||
(state: RootState) => state.notifications.items.toArray(),
|
||||
], (notifications) => notifications.map(([_, notification]) => notification).filter(item => item !== null));
|
||||
(_, topNotification?: string) => topNotification,
|
||||
], (notifications, topNotificationId) => {
|
||||
const allNotifications = notifications.map(([_, notification]) => notification).filter(item => item !== null);
|
||||
|
||||
if (topNotificationId) {
|
||||
const queuedNotificationCount = allNotifications.findIndex((notification) =>
|
||||
notification.most_recent_notification_id <= topNotificationId,
|
||||
);
|
||||
const displayedNotifications = allNotifications.slice(queuedNotificationCount);
|
||||
|
||||
return {
|
||||
queuedNotificationCount,
|
||||
displayedNotifications,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
queuedNotificationCount: 0,
|
||||
displayedNotifications: allNotifications,
|
||||
};
|
||||
});
|
||||
|
||||
const Notifications = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -40,11 +60,11 @@ const Notifications = () => {
|
|||
|
||||
const showFilterBar = settings.notifications.quickFilter.show;
|
||||
const activeFilter = settings.notifications.quickFilter.active;
|
||||
const notifications = useAppSelector(state => getNotifications(state));
|
||||
const [topNotification, setTopNotification] = useState<string>();
|
||||
const { queuedNotificationCount, displayedNotifications } = useAppSelector(state => getNotifications(state, topNotification));
|
||||
const isLoading = useAppSelector(state => state.notifications.isLoading);
|
||||
// const isUnread = useAppSelector(state => state.notifications.unread > 0);
|
||||
const hasMore = useAppSelector(state => state.notifications.hasMore);
|
||||
const totalQueuedNotificationsCount = useAppSelector(state => state.notifications.totalQueuedNotificationsCount || 0);
|
||||
|
||||
const scrollableContentRef = useRef<Array<JSX.Element> | null>(null);
|
||||
|
||||
|
@ -53,26 +73,26 @@ const Notifications = () => {
|
|||
// };
|
||||
|
||||
const handleLoadOlder = useCallback(debounce(() => {
|
||||
const minId = notifications.reduce<string | undefined>(
|
||||
const minId = displayedNotifications.reduce<string | undefined>(
|
||||
(minId, notification) => minId && notification.page_min_id && notification.page_min_id > minId
|
||||
? minId
|
||||
: notification.page_min_id,
|
||||
undefined,
|
||||
);
|
||||
dispatch(expandNotifications({ maxId: minId }));
|
||||
}, 300, { leading: true }), [notifications]);
|
||||
}, 300, { leading: true }), [displayedNotifications]);
|
||||
|
||||
const handleScroll = useCallback(debounce((startIndex?: number) => {
|
||||
dispatch(scrollTopNotifications(startIndex === 0));
|
||||
}, 100), []);
|
||||
|
||||
const handleMoveUp = (id: string) => {
|
||||
const elementIndex = notifications.findIndex(item => item !== null && item.group_key === id) - 1;
|
||||
const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) - 1;
|
||||
_selectChild(elementIndex);
|
||||
};
|
||||
|
||||
const handleMoveDown = (id: string) => {
|
||||
const elementIndex = notifications.findIndex(item => item !== null && item.group_key === id) + 1;
|
||||
const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) + 1;
|
||||
_selectChild(elementIndex);
|
||||
};
|
||||
|
||||
|
@ -84,7 +104,8 @@ const Notifications = () => {
|
|||
};
|
||||
|
||||
const handleDequeueNotifications = useCallback(() => {
|
||||
dispatch(dequeueNotifications());
|
||||
setTopNotification(undefined);
|
||||
dispatch(markReadNotifications());
|
||||
}, []);
|
||||
|
||||
const handleRefresh = useCallback(() => dispatch(expandNotifications()), []);
|
||||
|
@ -100,6 +121,11 @@ const Notifications = () => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (topNotification || displayedNotifications.length === 0) return;
|
||||
setTopNotification(displayedNotifications[0].most_recent_notification_id);
|
||||
}, [displayedNotifications.length]);
|
||||
|
||||
const emptyMessage = activeFilter === 'all'
|
||||
? <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." />;
|
||||
|
@ -112,8 +138,8 @@ const Notifications = () => {
|
|||
|
||||
if (isLoading && scrollableContentRef.current) {
|
||||
scrollableContent = scrollableContentRef.current;
|
||||
} else if (notifications.length > 0 || hasMore) {
|
||||
scrollableContent = notifications.map((item) => (
|
||||
} else if (displayedNotifications.length > 0 || hasMore) {
|
||||
scrollableContent = displayedNotifications.map((item) => (
|
||||
<Notification
|
||||
key={item.group_key}
|
||||
notification={item}
|
||||
|
@ -130,7 +156,7 @@ const Notifications = () => {
|
|||
const scrollContainer = (
|
||||
<ScrollableList
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && notifications.length === 0}
|
||||
showLoading={isLoading && displayedNotifications.length === 0}
|
||||
hasMore={hasMore}
|
||||
emptyMessage={emptyMessage}
|
||||
placeholderComponent={PlaceholderNotification}
|
||||
|
@ -138,7 +164,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.length === 0,
|
||||
'animate-pulse': displayedNotifications.length === 0,
|
||||
})}
|
||||
>
|
||||
{scrollableContent!}
|
||||
|
@ -152,7 +178,7 @@ const Notifications = () => {
|
|||
<Portal>
|
||||
<ScrollTopButton
|
||||
onClick={handleDequeueNotifications}
|
||||
count={totalQueuedNotificationsCount}
|
||||
count={queuedNotificationCount}
|
||||
message={messages.queue}
|
||||
/>
|
||||
</Portal>
|
||||
|
|
|
@ -20,41 +20,29 @@ import {
|
|||
NOTIFICATIONS_FILTER_SET,
|
||||
NOTIFICATIONS_CLEAR,
|
||||
NOTIFICATIONS_SCROLL_TOP,
|
||||
NOTIFICATIONS_UPDATE_QUEUE,
|
||||
NOTIFICATIONS_DEQUEUE,
|
||||
NOTIFICATIONS_MARK_READ_REQUEST,
|
||||
MAX_QUEUED_NOTIFICATIONS,
|
||||
} from '../actions/notifications';
|
||||
import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines';
|
||||
|
||||
import type { Notification as BaseNotification, Markers, NotificationGroup, PaginatedResponse, Relationship } from 'pl-api';
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const QueuedNotificationRecord = ImmutableRecord({
|
||||
notification: {} as any as BaseNotification,
|
||||
intlMessages: {} as Record<string, string>,
|
||||
intlLocale: '',
|
||||
});
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
items: ImmutableOrderedMap<string, NotificationGroup>(),
|
||||
hasMore: true,
|
||||
top: false,
|
||||
unread: 0,
|
||||
isLoading: false,
|
||||
queuedNotifications: ImmutableOrderedMap<string, QueuedNotification>(), //max = MAX_QUEUED_NOTIFICATIONS
|
||||
totalQueuedNotificationsCount: 0, //used for queuedItems overflow for MAX_QUEUED_NOTIFICATIONS+
|
||||
lastRead: -1 as string | -1,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
type QueuedNotification = ReturnType<typeof QueuedNotificationRecord>;
|
||||
|
||||
const parseId = (id: string | number) => parseInt(id as string, 10);
|
||||
|
||||
// For sorting the notifications
|
||||
const comparator = (a: Pick<NotificationGroup, 'group_key'>, b: Pick<NotificationGroup, 'group_key'>) => {
|
||||
const parse = (m: Pick<NotificationGroup, 'group_key'>) => parseId(m.group_key);
|
||||
const comparator = (a: Pick<NotificationGroup, 'most_recent_notification_id'>, b: Pick<NotificationGroup, 'most_recent_notification_id'>) => {
|
||||
const parse = (m: Pick<NotificationGroup, 'most_recent_notification_id'>) => parseId(m.most_recent_notification_id);
|
||||
if (parse(a) < parse(b)) return 1;
|
||||
if (parse(a) > parse(b)) return -1;
|
||||
return 0;
|
||||
|
@ -75,13 +63,7 @@ const importNotification = (state: State, notification: NotificationGroup) => {
|
|||
|
||||
if (!top) state = state.update('unread', unread => unread + 1);
|
||||
|
||||
return state.update('items', map => {
|
||||
if (top && map.size > 40) {
|
||||
map = map.take(20);
|
||||
}
|
||||
|
||||
return map.set(notification.group_key, notification).sort(comparator);
|
||||
});
|
||||
return state.update('items', map => map.set(notification.group_key, notification).sort(comparator));
|
||||
};
|
||||
|
||||
const expandNormalizedNotifications = (state: State, notifications: NotificationGroup[], next: (() => Promise<PaginatedResponse<BaseNotification>>) | null) => {
|
||||
|
@ -112,28 +94,6 @@ const deleteByStatus = (state: State, statusId: string) =>
|
|||
// @ts-ignore
|
||||
state.update('items', map => map.filterNot(item => item !== null && item.status === statusId));
|
||||
|
||||
const updateNotificationsQueue = (state: State, notification: BaseNotification, intlMessages: Record<string, string>, intlLocale: string) => {
|
||||
const queuedNotifications = state.queuedNotifications;
|
||||
const listedNotifications = state.items;
|
||||
const totalQueuedNotificationsCount = state.totalQueuedNotificationsCount;
|
||||
|
||||
const alreadyExists = queuedNotifications.has(notification.group_key) || listedNotifications.has(notification.group_key);
|
||||
if (alreadyExists) return state;
|
||||
|
||||
const newQueuedNotifications = queuedNotifications;
|
||||
|
||||
return state.withMutations(mutable => {
|
||||
if (totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
||||
mutable.set('queuedNotifications', newQueuedNotifications.set(notification.group_key, QueuedNotificationRecord({
|
||||
notification,
|
||||
intlMessages,
|
||||
intlLocale,
|
||||
})));
|
||||
}
|
||||
mutable.set('totalQueuedNotificationsCount', totalQueuedNotificationsCount + 1);
|
||||
});
|
||||
};
|
||||
|
||||
const importMarker = (state: State, marker: Markers) => {
|
||||
const lastReadId = marker.notifications.last_read_id || -1 as string | -1;
|
||||
|
||||
|
@ -163,13 +123,6 @@ const notifications = (state: State = ReducerRecord(), action: AccountsAction |
|
|||
return updateTop(state, action.top);
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return importNotification(state, action.notification);
|
||||
case NOTIFICATIONS_UPDATE_QUEUE:
|
||||
return updateNotificationsQueue(state, action.notification, action.intlMessages, action.intlLocale);
|
||||
case NOTIFICATIONS_DEQUEUE:
|
||||
return state.withMutations(mutable => {
|
||||
mutable.delete('queuedNotifications');
|
||||
mutable.set('totalQueuedNotificationsCount', 0);
|
||||
});
|
||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||
return expandNormalizedNotifications(state, action.notifications, action.next);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
|
|
Loading…
Reference in a new issue