From bfd01110d86001e29b746d026655982e5dab21ea Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 19 Aug 2024 17:59:06 +0200 Subject: [PATCH] [Glitch] Reload notifications when accepted notifications are merged (streaming only) Port 53c183f899b5382f1eebd72e34a090c30f8eba6a to glitch-soc Signed-off-by: Claire --- .../glitch/actions/notification_groups.ts | 43 ++++++++++++++++++- .../flavours/glitch/actions/streaming.js | 10 ++++- .../features/notifications_v2/index.tsx | 14 +++--- .../glitch/reducers/notification_groups.ts | 13 +++++- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/notification_groups.ts b/app/javascript/flavours/glitch/actions/notification_groups.ts index 49a1a1362f..3d970e57f3 100644 --- a/app/javascript/flavours/glitch/actions/notification_groups.ts +++ b/app/javascript/flavours/glitch/actions/notification_groups.ts @@ -138,8 +138,18 @@ export const processNewNotificationForGroups = createAppAsyncThunk( export const loadPending = createAction('notificationGroups/loadPending'); -export const updateScrollPosition = createAction<{ top: boolean }>( +export const updateScrollPosition = createAppAsyncThunk( 'notificationGroups/updateScrollPosition', + ({ top }: { top: boolean }, { dispatch, getState }) => { + if ( + top && + getState().notificationGroups.mergedNotifications === 'needs-reload' + ) { + void dispatch(fetchNotifications()); + } + + return { top }; + }, ); export const setNotificationsFilter = createAppAsyncThunk( @@ -165,5 +175,34 @@ export const markNotificationsAsRead = createAction( 'notificationGroups/markAsRead', ); -export const mountNotifications = createAction('notificationGroups/mount'); +export const mountNotifications = createAppAsyncThunk( + 'notificationGroups/mount', + (_, { dispatch, getState }) => { + const state = getState(); + + if ( + state.notificationGroups.mounted === 0 && + state.notificationGroups.mergedNotifications === 'needs-reload' + ) { + void dispatch(fetchNotifications()); + } + }, +); + export const unmountNotifications = createAction('notificationGroups/unmount'); + +export const refreshStaleNotificationGroups = createAppAsyncThunk<{ + deferredRefresh: boolean; +}>('notificationGroups/refreshStale', (_, { dispatch, getState }) => { + const state = getState(); + + if ( + state.notificationGroups.scrolledToTop || + !state.notificationGroups.mounted + ) { + void dispatch(fetchNotifications()); + return { deferredRefresh: false }; + } + + return { deferredRefresh: true }; +}); diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index f7f7755acd..f5beff55d6 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -10,7 +10,7 @@ import { deleteAnnouncement, } from './announcements'; import { updateConversations } from './conversations'; -import { processNewNotificationForGroups } from './notification_groups'; +import { processNewNotificationForGroups, refreshStaleNotificationGroups } from './notification_groups'; import { updateNotifications, expandNotifications } from './notifications'; import { updateStatus } from './statuses'; import { @@ -108,6 +108,14 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti } break; } + case 'notifications_merged': + const state = getState(); + if (state.notifications.top || !state.notifications.mounted) + dispatch(expandNotifications({ forceLoad: true, maxId: undefined })); + if(state.settings.getIn(['notifications', 'groupingBeta'], false)) { + dispatch(refreshStaleNotificationGroups()); + } + break; case 'conversation': // @ts-expect-error dispatch(updateConversations(JSON.parse(data.payload))); diff --git a/app/javascript/flavours/glitch/features/notifications_v2/index.tsx b/app/javascript/flavours/glitch/features/notifications_v2/index.tsx index 84fde80008..f5bf7adda3 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/index.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/index.tsx @@ -81,7 +81,11 @@ export const Notifications: React.FC<{ const anyPendingNotification = useAppSelector(selectAnyPendingNotification); - const isUnread = unreadNotificationsCount > 0; + const needsReload = useAppSelector( + (state) => state.notificationGroups.mergedNotifications === 'needs-reload', + ); + + const isUnread = unreadNotificationsCount > 0 || needsReload; const canMarkAsRead = useAppSelector(selectSettingsNotificationsShowUnread) && @@ -118,11 +122,11 @@ export const Notifications: React.FC<{ // Keep track of mounted components for unread notification handling useEffect(() => { - dispatch(mountNotifications()); + void dispatch(mountNotifications()); return () => { dispatch(unmountNotifications()); - dispatch(updateScrollPosition({ top: false })); + void dispatch(updateScrollPosition({ top: false })); }; }, [dispatch]); @@ -147,11 +151,11 @@ export const Notifications: React.FC<{ }, [dispatch]); const handleScrollToTop = useDebouncedCallback(() => { - dispatch(updateScrollPosition({ top: true })); + void dispatch(updateScrollPosition({ top: true })); }, 100); const handleScroll = useDebouncedCallback(() => { - dispatch(updateScrollPosition({ top: false })); + void dispatch(updateScrollPosition({ top: false })); }, 100); useEffect(() => { diff --git a/app/javascript/flavours/glitch/reducers/notification_groups.ts b/app/javascript/flavours/glitch/reducers/notification_groups.ts index a721e4008d..af8e7a5e8c 100644 --- a/app/javascript/flavours/glitch/reducers/notification_groups.ts +++ b/app/javascript/flavours/glitch/reducers/notification_groups.ts @@ -19,6 +19,7 @@ import { markNotificationsAsRead, mountNotifications, unmountNotifications, + refreshStaleNotificationGroups, } from 'flavours/glitch/actions/notification_groups'; import { disconnectTimeline, @@ -51,6 +52,7 @@ interface NotificationGroupsState { readMarkerId: string; mounted: number; isTabVisible: boolean; + mergedNotifications: 'ok' | 'pending' | 'needs-reload'; } const initialState: NotificationGroupsState = { @@ -58,6 +60,8 @@ const initialState: NotificationGroupsState = { pendingGroups: [], // holds pending groups in slow mode scrolledToTop: false, isLoading: false, + // this is used to track whether we need to refresh notifications after accepting requests + mergedNotifications: 'ok', // The following properties are used to track unread notifications lastReadId: '0', // used internally for unread notifications readMarkerId: '0', // user-facing and updated when focus changes @@ -301,6 +305,7 @@ export const notificationGroupsReducer = createReducer( json.type === 'gap' ? json : createNotificationGroupFromJSON(json), ); state.isLoading = false; + state.mergedNotifications = 'ok'; updateLastReadId(state); }) .addCase(fetchNotificationsGap.fulfilled, (state, action) => { @@ -455,7 +460,7 @@ export const notificationGroupsReducer = createReducer( state.groups = state.pendingGroups.concat(state.groups); state.pendingGroups = []; }) - .addCase(updateScrollPosition, (state, action) => { + .addCase(updateScrollPosition.fulfilled, (state, action) => { state.scrolledToTop = action.payload.top; updateLastReadId(state); trimNotifications(state); @@ -482,7 +487,7 @@ export const notificationGroupsReducer = createReducer( action.payload.markers.notifications.last_read_id; } }) - .addCase(mountNotifications, (state) => { + .addCase(mountNotifications.fulfilled, (state) => { state.mounted += 1; commitLastReadId(state); updateLastReadId(state); @@ -498,6 +503,10 @@ export const notificationGroupsReducer = createReducer( .addCase(unfocusApp, (state) => { state.isTabVisible = false; }) + .addCase(refreshStaleNotificationGroups.fulfilled, (state, action) => { + if (action.payload.deferredRefresh) + state.mergedNotifications = 'needs-reload'; + }) .addMatcher( isAnyOf(authorizeFollowRequestSuccess, rejectFollowRequestSuccess), (state, action) => {