Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

[MM-13663][MM-11504] Adds bi-directional scrolling and use unread API on channel load #2274

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
MM-13663 Use unread API and add bi-directional scrolling
  * Refactor post_list to have a wrapper for calling
    respective posts
  * Use unread API for loading posts on load.
  * Use since or before_id based on the channel status
    of posts.
  * Clear postInChannel when entering from a view which is not at
    latest post
  * Take a backup before clearing posts as we might need it incase of
    network issues.
  * Always use oldest postId or newest postId for loading posts.
  * Fix async render of channelView by using a flag on channelView.
  * Change dependency of post_list to have channeId instead of channel.
  * Add channel selector for channel_intro_message component.
  * Add constants for AFTER_ID and BEFORE_ID.
  • Loading branch information
sudheerDev committed Jan 15, 2019
commit 5119abb43f3f7f5fc43c76b65d5f37deba8d6be5
27 changes: 19 additions & 8 deletions actions/post_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Constants,
RHSStates,
StoragePrefixes,
PostRequestTypes,
} from 'utils/constants';
import {EMOJI_PATTERN} from 'utils/emoticons.jsx';
import * as UserAgent from 'utils/user_agent';
Expand Down Expand Up @@ -132,13 +133,9 @@ export function addReaction(postId, emojiName) {
const POST_INCREASE_AMOUNT = Constants.POST_CHUNK_SIZE / 2;

// Returns true if there are more posts to load
export function increasePostVisibility(channelId, focusedPostId) {
export function loadPosts({channelId, postId, type}) {
return async (dispatch, getState) => {
const state = getState();
if (state.views.channel.loadingPosts[channelId]) {
return true;
}

const currentPostVisibility = state.views.channel.postVisibility[channelId];

if (currentPostVisibility >= Constants.MAX_POST_VISIBILITY) {
Expand All @@ -151,11 +148,13 @@ export function increasePostVisibility(channelId, focusedPostId) {
channelId,
});

const page = Math.floor(currentPostVisibility / POST_INCREASE_AMOUNT);
const page = 0;

let result;
if (focusedPostId) {
result = await dispatch(PostActions.getPostsBefore(channelId, focusedPostId, page, POST_INCREASE_AMOUNT));
if (type === PostRequestTypes.BEFORE_ID) {
result = await dispatch(PostActions.getPostsBefore(channelId, postId, page, POST_INCREASE_AMOUNT));
} else if (type === PostRequestTypes.AFTER_ID) {
result = await dispatch(PostActions.getPostsAfter(channelId, postId, page, POST_INCREASE_AMOUNT));
} else {
result = await dispatch(PostActions.getPosts(channelId, page, POST_INCREASE_AMOUNT));
}
Expand Down Expand Up @@ -183,6 +182,18 @@ export function increasePostVisibility(channelId, focusedPostId) {
};
}

export function loadUnreads(channelId) {
return (dispatch) => {
dispatch({
type: ActionTypes.INCREASE_POST_VISIBILITY,
data: channelId,
amount: Constants.POST_CHUNK_SIZE,
});

return dispatch(PostActions.getPostsUnread(channelId));
};
}

export function searchForTerm(term) {
return (dispatch) => {
dispatch(RhsActions.updateSearchTerms(term));
Expand Down
6 changes: 3 additions & 3 deletions actions/post_actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,10 @@ describe('Actions.Posts', () => {
expect(testStore.getActions()).toEqual([{type: ActionTypes.HIDE_EDIT_POST_MODAL}]);
});

test('increasePostVisibility', async () => {
test('loadPosts', async () => {
const testStore = await mockStore(initialState);

await testStore.dispatch(Actions.increasePostVisibility('current_channel_id'));
await testStore.dispatch(Actions.loadPosts('current_channel_id'));
expect(testStore.getActions()).toEqual([
{channelId: 'current_channel_id', data: true, type: 'LOADING_POSTS'},
{args: ['current_channel_id', 2, 30], type: 'MOCK_GET_POSTS'},
Expand All @@ -242,7 +242,7 @@ describe('Actions.Posts', () => {
},
]);

await testStore.dispatch(Actions.increasePostVisibility('current_channel_id', 'latest_post_id'));
await testStore.dispatch(Actions.loadPosts('current_channel_id', 'latest_post_id'));
expect(testStore.getActions()).toEqual([
{channelId: 'current_channel_id', data: true, type: 'LOADING_POSTS'},
{args: ['current_channel_id', 2, 30], type: 'MOCK_GET_POSTS'},
Expand Down
40 changes: 40 additions & 0 deletions actions/views/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// See LICENSE.txt for license information.

import {leaveChannel as leaveChannelRedux, joinChannel, unfavoriteChannel} from 'mattermost-redux/actions/channels';
import {getPostsSince, getPostsBefore} from 'mattermost-redux/actions/posts';
import {getChannel, getChannelByName, getCurrentChannel, getDefaultChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentRelativeTeamUrl, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId, getUserByUsername} from 'mattermost-redux/selectors/entities/users';
import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
import {isFavoriteChannel} from 'mattermost-redux/utils/channel_utils';
import {getNewestPostIdFromPosts, getOldestPostIdFromPosts} from 'mattermost-redux/utils/post_utils';
import {autocompleteUsers} from 'mattermost-redux/actions/users';

import {openDirectChannelToUserId} from 'actions/channel_actions.jsx';
Expand All @@ -16,6 +18,8 @@ import {browserHistory} from 'utils/browser_history';
import {Constants, ActionTypes} from 'utils/constants.jsx';
import {isMobile} from 'utils/utils.jsx';

const POST_INCREASE_AMOUNT = Constants.POST_CHUNK_SIZE / 2;

export function checkAndSetMobileView() {
return (dispatch) => {
dispatch({
Expand Down Expand Up @@ -117,3 +121,39 @@ export function autocompleteUsersInChannel(prefix, channelId) {
};
}

export function changeChannelPostsStatus(params) {
// takes params in the format of {channelId, atEnd: true/false} or {channelId, atStart: true/false}

return (dispatch) => {
dispatch({
type: ActionTypes.CHANNEL_POSTS_STATUS,
data: params,
});
};
}

export function channelSyncCompleted(channelId) {
return async (dispatch) => {
dispatch({
type: ActionTypes.CHANNEL_SYNC_STATUS,
data: channelId,
});
};
}

export function syncChannelPosts({channelId, channelPostsStatus, lastDisconnectAt, posts}) {
return async (dispatch) => {
if (channelPostsStatus.atEnd) {
await dispatch(getPostsSince(channelId, lastDisconnectAt));
} else {
let data;
const oldestPostId = getOldestPostIdFromPosts(posts);
let newestMessageId = getNewestPostIdFromPosts(posts);
do {
({data} = await dispatch(getPostsBefore(channelId, newestMessageId, 0, POST_INCREASE_AMOUNT))); // eslint-disable-line no-await-in-loop
newestMessageId = data.order[data.order.length - 1];
} while (data && !data.posts[oldestPostId]);
}
dispatch(channelSyncCompleted(channelId));
};
}
23 changes: 20 additions & 3 deletions actions/websocket_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ import {
import {setServerVersion} from 'mattermost-redux/actions/general';
import {clearErrors, logError} from 'mattermost-redux/actions/errors';

import {getPosts, getProfilesAndStatusesForPosts, getCustomEmojiForReaction} from 'mattermost-redux/actions/posts';
import {getProfilesAndStatusesForPosts, getCustomEmojiForReaction} from 'mattermost-redux/actions/posts';
import * as TeamActions from 'mattermost-redux/actions/teams';
import {getMe, getStatusesByIds, getProfilesByIds} from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
import {getCurrentUser, getCurrentUserId, getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
import {getMyTeams, getCurrentRelativeTeamUrl, getCurrentTeamId, getCurrentTeamUrl} from 'mattermost-redux/selectors/entities/teams';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getChannel, getCurrentChannel, getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getMyChannelMemberships} from 'mattermost-redux/selectors/entities/common';

import {openModal} from 'actions/views/modals';
import {incrementWsErrorCount, resetWsErrorCount} from 'actions/views/system';
Expand Down Expand Up @@ -129,6 +130,10 @@ export function reconnect(includeWebSocket = true) {
reconnectWebSocket();
}

dispatch({
type: GeneralTypes.WEBSOCKET_SUCCESS,
});

loadPluginsIfNecessary();

Object.values(pluginReconnectHandlers).forEach((handler) => {
Expand All @@ -140,7 +145,6 @@ export function reconnect(includeWebSocket = true) {
const currentTeamId = getState().entities.teams.currentTeamId;
if (currentTeamId) {
dispatch(loadChannelsForCurrentUser());
dispatch(getPosts(getCurrentChannelId(getState())));
StatusActions.loadStatusesForChannelAndSidebar();
dispatch(TeamActions.getMyTeamUnreads());
}
Expand Down Expand Up @@ -190,13 +194,26 @@ export function unregisterAllPluginWebSocketEvents(pluginId) {
}

function handleFirstConnect() {
dispatch(clearErrors);
dispatch(batchActions([
{type: GeneralTypes.WEBSOCKET_SUCCESS},
]));
}

function handleClose(failCount) {
if (failCount > MAX_WEBSOCKET_FAILS) {
dispatch(logError({type: 'critical', message: AnnouncementBarMessages.WEBSOCKET_PORT_ERROR}, true));
}

dispatch(batchActions([{
type: GeneralTypes.WEBSOCKET_FAILURE,
}, {
type: ActionTypes.ALL_CHANNEL_SYNC_STATUS,
data: {
channelIds: Object.keys(getMyChannelMemberships(getState())),
status: false,
},
}]));

dispatch(incrementWsErrorCount());
}

Expand Down
21 changes: 18 additions & 3 deletions components/channel_view/channel_view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PostView from 'components/post_view';
import TutorialView from 'components/tutorial';
import {clearMarks, mark, measure, trackEvent} from 'actions/diagnostics_actions.jsx';
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import LoadingScreen from 'components/loading_screen.jsx';

export default class ChannelView extends React.PureComponent {
static propTypes = {
Expand All @@ -26,6 +27,12 @@ export default class ChannelView extends React.PureComponent {
showTutorial: PropTypes.bool.isRequired,
channelIsArchived: PropTypes.bool.isRequired,
viewArchivedChannels: PropTypes.bool.isRequired,

/**
* For indicating channelLoading
* To prevent child views from loading with wrong channel.
*/
channelLoading: PropTypes.bool,
actions: PropTypes.shape({
goToLastViewedChannel: PropTypes.func.isRequired,
}),
Expand Down Expand Up @@ -162,9 +169,17 @@ export default class ChannelView extends React.PureComponent {
<ChannelHeader
channelId={this.props.channelId}
/>
<DeferredPostView
channelId={this.props.channelId}
/>
{ this.props.channelLoading ? (
<LoadingScreen
position='relative'
style={{height: 'calc(100% - 139px)'}}
key='loading'
/>
) : (
<DeferredPostView
channelId={this.props.channelId}
/>
)}
{createPost}
</div>
);
Expand Down
29 changes: 25 additions & 4 deletions components/channel_view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {createSelector} from 'reselect';
import {withRouter} from 'react-router-dom';
import {getInt} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {withRouter} from 'react-router-dom';
import {getTeamByName} from 'mattermost-redux/selectors/entities/teams';

import {getDirectTeammate} from 'utils/utils.jsx';
import {TutorialSteps, Preferences} from 'utils/constants.jsx';
import {TutorialSteps, Preferences, Constants} from 'utils/constants.jsx';

import {goToLastViewedChannel} from 'actions/views/channel';

Expand All @@ -27,20 +28,40 @@ const getDeactivatedChannel = createSelector(
}
);

function mapStateToProps(state) {
function mapStateToProps(state, ownProps) {
const channel = getCurrentChannel(state);

let channelLoading = false;
const config = getConfig(state);
const enableTutorial = config.EnableTutorial === 'true';
const tutorialStep = getInt(state, Preferences.TUTORIAL_STEP, getCurrentUserId(state), TutorialSteps.FINISHED);
const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';
const team = getTeamByName(state, ownProps.match.params.team);

if (channel) {
if (channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL) {
if (channel.name !== ownProps.match.params.identifier) {
channelLoading = true;
}

if (channel.team_id && channel.team_id !== team.id) {
channelLoading = true;
}
}
} else {
channelLoading = true;
}

if (channel && (channel.team_id && channel.team_id !== team.id)) {
channelLoading = true;
}

return {
channelId: channel ? channel.id : '',
deactivatedChannel: channel ? getDeactivatedChannel(state, channel.id) : false,
showTutorial: enableTutorial && tutorialStep <= TutorialSteps.INTRO_SCREENS,
channelIsArchived: channel ? channel.delete_at !== 0 : false,
viewArchivedChannels,
channelLoading,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ exports[`components/PermalinkView should match snapshot 1`] = `
<withRouter(Connect(ChannelHeader))
channelId="channel_id"
/>
<Connect(PostList)
<Connect(PostView)
channelId="channel_id"
focusedPostId="post_id"
/>
Expand Down Expand Up @@ -41,7 +41,7 @@ exports[`components/PermalinkView should match snapshot with archived channel 1`
<withRouter(Connect(ChannelHeader))
channelId="channel_id"
/>
<Connect(PostList)
<Connect(PostView)
channelId="channel_id"
focusedPostId="post_id"
/>
Expand Down
6 changes: 5 additions & 1 deletion components/post_view/channel_intro_message/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import {connect} from 'react-redux';

import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {isCurrentChannelReadOnly} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentChannel, isCurrentChannelReadOnly} from 'mattermost-redux/selectors/entities/channels';
import {getProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
import {get} from 'mattermost-redux/selectors/entities/preferences';

import {Preferences} from 'utils/constants.jsx';
import {getCurrentLocale} from 'selectors/i18n';

import ChannelIntroMessage from './channel_intro_message.jsx';
Expand All @@ -17,10 +19,12 @@ function mapStateToProps(state) {
const isReadOnly = isCurrentChannelReadOnly(state);

return {
channel: getCurrentChannel(state),
locale: getCurrentLocale(state),
channelProfiles: getProfilesInCurrentChannel(state),
enableUserCreation,
isReadOnly,
fullWidth: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
};
}

Expand Down
Loading