MDL-64715 message: improve the front-end for the self-conversations
authorRyan Wyllie <ryan@ryanwyllie.com>
Tue, 16 Apr 2019 08:33:25 +0000 (16:33 +0800)
committerSara Arjona <sara@moodle.com>
Tue, 16 Apr 2019 10:06:28 +0000 (12:06 +0200)
message/amd/build/message_drawer_view_conversation.min.js
message/amd/build/message_drawer_view_conversation_constants.min.js
message/amd/build/message_drawer_view_conversation_patcher.min.js
message/amd/build/message_drawer_view_overview.min.js
message/amd/build/message_drawer_view_overview_section.min.js
message/amd/src/message_drawer_view_conversation.js
message/amd/src/message_drawer_view_conversation_constants.js
message/amd/src/message_drawer_view_conversation_patcher.js
message/amd/src/message_drawer_view_overview.js
message/amd/src/message_drawer_view_overview_section.js

index f681f92..21837f5 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation.min.js and b/message/amd/build/message_drawer_view_conversation.min.js differ
index 464ad1e..2c2761d 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_constants.min.js and b/message/amd/build/message_drawer_view_conversation_constants.min.js differ
index c818b03..1fdea1f 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_patcher.min.js and b/message/amd/build/message_drawer_view_conversation_patcher.min.js differ
index aa0cded..304ea70 100644 (file)
Binary files a/message/amd/build/message_drawer_view_overview.min.js and b/message/amd/build/message_drawer_view_overview.min.js differ
index 7717264..9b2bbce 100644 (file)
Binary files a/message/amd/build/message_drawer_view_overview_section.min.js and b/message/amd/build/message_drawer_view_overview_section.min.js differ
index 6081051..1b4050b 100644 (file)
@@ -120,7 +120,7 @@ function(
      * @return {Number} Userid.
      */
     var getOtherUserId = function() {
-        if (!viewState || (viewState.type != CONVERSATION_TYPES.PRIVATE && viewState.type != CONVERSATION_TYPES.SELF)) {
+        if (!viewState || viewState.type == CONVERSATION_TYPES.PUBLIC) {
             return null;
         }
 
@@ -149,7 +149,7 @@ function(
             if (!carry) {
                 var state = stateCache[id].state;
 
-                if (state.type == CONVERSATION_TYPES.PRIVATE || state.type == CONVERSATION_TYPES.SELF) {
+                if (state.type != CONVERSATION_TYPES.PUBLIC) {
                     if (userId in state.members) {
                         // We've found a cached conversation for this user!
                         carry = state.id;
@@ -274,6 +274,9 @@ function(
      */
     var loadEmptyPrivateConversation = function(loggedInUserProfile, otherUserId) {
         var loggedInUserId = loggedInUserProfile.id;
+        // If the other user id is the same as the logged in user then this is a self
+        // conversation.
+        var conversationType = loggedInUserId == otherUserId ? CONVERSATION_TYPES.SELF : CONVERSATION_TYPES.PRIVATE;
         var newState = StateManager.setLoadingMembers(viewState, true);
         newState = StateManager.setLoadingMessages(newState, true);
         return render(newState)
@@ -288,57 +291,16 @@ function(
                 }
             })
             .then(function(profile) {
-                var newState = StateManager.addMembers(viewState, [profile, loggedInUserProfile]);
+                // If the conversation is a self conversation then the profile loaded is the
+                // logged in user so only add that to the members array.
+                var members = conversationType == CONVERSATION_TYPES.SELF ? [profile] : [profile, loggedInUserProfile];
+                var newState = StateManager.addMembers(viewState, members);
                 newState = StateManager.setLoadingMembers(newState, false);
                 newState = StateManager.setLoadingMessages(newState, false);
                 newState = StateManager.setName(newState, profile.fullname);
-                newState = StateManager.setType(newState, CONVERSATION_TYPES.PRIVATE);
+                newState = StateManager.setType(newState, conversationType);
                 newState = StateManager.setImageUrl(newState, profile.profileimageurl);
-                newState = StateManager.setTotalMemberCount(newState, 2);
-                return render(newState)
-                    .then(function() {
-                        return profile;
-                    });
-            })
-            .catch(function(error) {
-                var newState = StateManager.setLoadingMembers(viewState, false);
-                render(newState);
-                Notification.exception(error);
-            });
-    };
-
-    /**
-     * Load up an empty self-conversation for the logged in user.
-     * Sets all of the conversation details based on the current user.
-     *
-     * A conversation isn't created until the user sends the first message.
-     *
-     * @param  {Object} loggedInUserProfile The logged in user profile.
-     * @return {Object} Profile returned from repository.
-     */
-    var loadEmptySelfConversation = function(loggedInUserProfile) {
-        var loggedInUserId = loggedInUserProfile.id;
-        var newState = StateManager.setLoadingMembers(viewState, true);
-        newState = StateManager.setLoadingMessages(newState, true);
-        return render(newState)
-            .then(function() {
-                return Repository.getMemberInfo(loggedInUserId, [loggedInUserId], true, true);
-            })
-            .then(function(profiles) {
-                if (profiles.length) {
-                    return profiles[0];
-                } else {
-                    throw new Error('Unable to load other user profile');
-                }
-            })
-            .then(function(profile) {
-                var newState = StateManager.addMembers(viewState, [profile, loggedInUserProfile]);
-                newState = StateManager.setLoadingMembers(newState, false);
-                newState = StateManager.setLoadingMessages(newState, false);
-                newState = StateManager.setName(newState, profile.fullname);
-                newState = StateManager.setType(newState, CONVERSATION_TYPES.SELF);
-                newState = StateManager.setImageUrl(newState, profile.profileimageurl);
-                newState = StateManager.setTotalMemberCount(newState, 1);
+                newState = StateManager.setTotalMemberCount(newState, members.length);
                 return render(newState)
                     .then(function() {
                         return profile;
@@ -373,7 +335,8 @@ function(
 
         var name = conversation.name;
         var imageUrl = conversation.imageurl;
-        if (conversation.type == CONVERSATION_TYPES.PRIVATE || conversation.type == CONVERSATION_TYPES.SELF) {
+
+        if (conversation.type != CONVERSATION_TYPES.PUBLIC) {
             name = name || otherUser ? otherUser.fullname : '';
             imageUrl = imageUrl || otherUser ? otherUser.profileimageurl : '';
         }
@@ -1076,8 +1039,7 @@ function(
         var newConversationId = null;
         return render(newState)
             .then(function() {
-                if (!conversationId &&
-                    (viewState.type == CONVERSATION_TYPES.PRIVATE || viewState.type == CONVERSATION_TYPES.SELF)) {
+                if (!conversationId && (viewState.type != CONVERSATION_TYPES.PUBLIC)) {
                     // If it's a new private conversation then we need to use the old
                     // web service function to create the conversation.
                     var otherUserId = getOtherUserId();
@@ -1539,50 +1501,39 @@ function(
     var resetNoConversation = function(body, loggedInUserProfile, otherUserId) {
         // Always reset the state back to the initial state so that the
         // state manager and patcher can work correctly.
-        if (loggedInUserProfile.id != otherUserId) {
-            // This is a private conversation between two users.
-            return resetState(body, null, loggedInUserProfile)
-                .then(function() {
+        return resetState(body, null, loggedInUserProfile)
+            .then(function() {
+                if (loggedInUserProfile.id != otherUserId) {
+                    // Private conversation between two different users.
                     return Repository.getConversationBetweenUsers(
-                            loggedInUserProfile.id,
-                            otherUserId,
-                            true,
-                            true,
-                            0,
-                            0,
-                            LOAD_MESSAGE_LIMIT,
-                            0,
-                            NEWEST_FIRST
-                        )
-                        .then(function(conversation) {
-                            // Looks like we have a conversation after all! Let's use that.
-                            return resetByConversation(body, conversation, loggedInUserProfile);
-                        })
-                        .catch(function() {
-                            // Can't find a conversation. Oh well. Just load up a blank one.
-                            return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);
-                        });
-                });
-        } else {
-            // This is a self-conversation.
-            return resetState(body, null, loggedInUserProfile)
-                .then(function() {
+                        loggedInUserProfile.id,
+                        otherUserId,
+                        true,
+                        true,
+                        0,
+                        0,
+                        LOAD_MESSAGE_LIMIT,
+                        0,
+                        NEWEST_FIRST
+                    );
+                } else {
+                    // Self conversation.
                     return Repository.getSelfConversation(
-                            loggedInUserProfile.id,
-                            LOAD_MESSAGE_LIMIT,
-                            0,
-                            NEWEST_FIRST
-                        )
-                        .then(function(conversation) {
-                            // Looks like we have a conversation after all! Let's use that.
-                            return resetByConversation(body, conversation, loggedInUserProfile);
-                        })
-                        .catch(function() {
-                            // Can't find a conversation. Oh well. Just load up a blank one.
-                            return loadEmptySelfConversation(loggedInUserProfile);
-                        });
-                });
-        }
+                        loggedInUserProfile.id,
+                        LOAD_MESSAGE_LIMIT,
+                        0,
+                        NEWEST_FIRST
+                    );
+                }
+            })
+            .then(function(conversation) {
+                // Looks like we have a conversation after all! Let's use that.
+                return resetByConversation(body, conversation, loggedInUserProfile);
+            })
+            .catch(function() {
+                // Can't find a conversation. Oh well. Just load up a blank one.
+                return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);
+            });
     };
 
     /**
index 5c1e4f0..04b6584 100644 (file)
@@ -102,19 +102,10 @@ define([], function() {
         SELF: 3
     };
 
-    // Categories displayed in the message drawer. Some methods (such as filterCountsByType) are expecting their value
-    // will be the same as the defined in the CONVERSATION_TYPES, except for the favourite.
-    var CONVERSATION_CATEGORY_TYPES = {
-        PRIVATE: 1,
-        PUBLIC: 2,
-        FAVOURITE: null
-    };
-
     return {
         SELECTORS: SELECTORS,
         TEMPLATES: TEMPLATES,
         CONVERSATION_TYPES: CONVERSATION_TYPES,
-        CONVERSATION_CATEGORY_TYPES: CONVERSATION_CATEGORY_TYPES,
         NEWEST_MESSAGES_FIRST: true,
         LOAD_MESSAGE_LIMIT: 100,
         INITIAL_NEW_MESSAGE_POLL_TIMEOUT: 1000
index a0a297c..1d59260 100644 (file)
@@ -322,7 +322,7 @@ function(
      * @return {Object} patch
      */
     var buildHeaderPatchTypeSelf = function(state, newState) {
-        var shouldRenderHeader = (state.name === null);
+        var shouldRenderHeader = (state.name === null && newState.name !== null);
 
         if (shouldRenderHeader) {
             return {
@@ -1150,7 +1150,11 @@ function(
      * @return {bool}
      */
     var buildSelfConversationMessage = function(state, newState) {
-        return (newState.type == Constants.CONVERSATION_TYPES.SELF);
+        if (state.type != newState.type) {
+            return (newState.type == Constants.CONVERSATION_TYPES.SELF);
+        }
+
+        return null;
     };
 
     /**
index d126748..320fc38 100644 (file)
@@ -55,6 +55,14 @@ function(
         SECTION_TOGGLE_BUTTON: '[data-toggle]'
     };
 
+    // Categories displayed in the message drawer. Some methods (such as filterCountsByType) are expecting their value
+    // will be the same as the defined in the CONVERSATION_TYPES, except for the favourite.
+    var OVERVIEW_SECTION_TYPES = {
+        PRIVATE: [Constants.CONVERSATION_TYPES.PRIVATE, Constants.CONVERSATION_TYPES.SELF],
+        PUBLIC: [Constants.CONVERSATION_TYPES.PUBLIC],
+        FAVOURITE: null
+    };
+
     var loadAllCountsPromise = null;
 
     /**
@@ -81,18 +89,23 @@ function(
      * This is used on the result returned by the loadAllCounts function.
      *
      * @param {Object} counts Conversation counts indexed by conversation type.
-     * @param {String|null} type The conversation type (null for favourites only).
+     * @param {Array|null} types The conversation types handlded by this section (null for all conversation types).
+     * @param {bool} includeFavourites If this section includes favourites
      * @return {Number}
      */
-    var filterCountsByType = function(counts, type) {
+    var filterCountsByTypes = function(counts, types, includeFavourites) {
         var total = 0;
-        if (type === Constants.CONVERSATION_CATEGORY_TYPES.PRIVATE && counts.types[Constants.CONVERSATION_TYPES.SELF]) {
-            // As private and self conversations are displayed together, we need to add the counts for the self-conversations
-            // to the private ones, when there is any self-conversation.
-            total = counts.types[Constants.CONVERSATION_TYPES.PRIVATE] + counts.types[Constants.CONVERSATION_TYPES.SELF];
-        } else {
-            total = type === Constants.CONVERSATION_CATEGORY_TYPES.FAVOURITE ? counts.favourites : counts.types[type];
+
+        if (types && types.length) {
+            total = types.reduce(function(carry, type) {
+                return carry + counts.types[type];
+            }, total);
         }
+
+        if (includeFavourites) {
+            total += counts.favourites;
+        }
+
         return total;
     };
 
@@ -230,34 +243,35 @@ function(
 
         var sections = [
             // Favourite conversations section.
-            [body.find(SELECTORS.FAVOURITES), Constants.CONVERSATION_CATEGORY_TYPES.FAVOURITE, true],
+            [body.find(SELECTORS.FAVOURITES), OVERVIEW_SECTION_TYPES.FAVOURITE, true],
             // Group conversations section.
-            [body.find(SELECTORS.GROUP_MESSAGES), Constants.CONVERSATION_CATEGORY_TYPES.PUBLIC, false],
+            [body.find(SELECTORS.GROUP_MESSAGES), OVERVIEW_SECTION_TYPES.PUBLIC, false],
             // Private conversations section.
-            [body.find(SELECTORS.MESSAGES), Constants.CONVERSATION_CATEGORY_TYPES.PRIVATE, false]
+            [body.find(SELECTORS.MESSAGES), OVERVIEW_SECTION_TYPES.PRIVATE, false]
         ];
 
         sections.forEach(function(args) {
             var sectionRoot = args[0];
-            var sectionType = args[1];
+            var sectionTypes = args[1];
             var includeFavourites = args[2];
             var totalCountPromise = allCounts.then(function(result) {
-                return filterCountsByType(result.total, sectionType);
+                return filterCountsByTypes(result.total, sectionTypes, includeFavourites);
             });
             var unreadCountPromise = allCounts.then(function(result) {
-                return filterCountsByType(result.unread, sectionType);
+                return filterCountsByTypes(result.unread, sectionTypes, includeFavourites);
             });
 
-            Section.show(namespace, null, sectionRoot, null, sectionType, includeFavourites,
+            Section.show(namespace, null, sectionRoot, null, sectionTypes, includeFavourites,
                 totalCountPromise, unreadCountPromise);
         });
 
         return allCounts.then(function(result) {
                 var sectionParams = sections.map(function(section) {
                     var sectionRoot = section[0];
-                    var sectionType = section[1];
-                    var totalCount = filterCountsByType(result.total, sectionType);
-                    var unreadCount = filterCountsByType(result.unread, sectionType);
+                    var sectionTypes = section[1];
+                    var includeFavourites = section[2];
+                    var totalCount = filterCountsByTypes(result.total, sectionTypes, includeFavourites);
+                    var unreadCount = filterCountsByTypes(result.unread, sectionTypes, includeFavourites);
 
                     return [sectionRoot, totalCount, unreadCount];
                 });
index a0ab83c..da585eb 100644 (file)
@@ -233,12 +233,34 @@ function(
     /**
      * Build the callback to load conversations.
      *
-     * @param  {Number} type The conversation type.
+     * @param  {Array|null} types The conversation types for this section.
      * @param  {bool} includeFavourites Include/exclude favourites.
      * @param  {Number} offset Result offset
      * @return {Function}
      */
-    var getLoadCallback = function(type, includeFavourites, offset) {
+    var getLoadCallback = function(types, includeFavourites, offset) {
+        // Note: This function is a bit messy because we've added the concept of loading
+        // multiple conversations types (e.g. private + self) at once but haven't properly
+        // updated the web service to accept an array of types. Instead we've added a new
+        // parameter for the self type which means we can only ever load self + other type.
+        // This should be improved to make it more extensible in the future. Adding new params
+        // for each type isn't very scalable.
+        var type = null;
+        // Include self conversations in the results by default.
+        var includeSelfConversations = true;
+        if (types && types.length) {
+            // Just get the conversation types that aren't "self" for now.
+            var nonSelfConversationTypes = types.filter(function(candidate) {
+                return candidate != MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF;
+            });
+            // If we're specifically asking for a list of types that doesn't include the self
+            // conversations then we don't need to include them.
+            includeSelfConversations = types.length != nonSelfConversationTypes.length;
+            // As mentioned above the webservice is currently limited to loading one type at a
+            // time (plus self conversations) so let's hope we never change this.
+            type = nonSelfConversationTypes[0];
+        }
+
         return function(root, userId) {
             return MessageRepository.getConversations(
                     userId,
@@ -246,7 +268,7 @@ function(
                     LOAD_LIMIT + 1,
                     offset,
                     includeFavourites,
-                    true // Always merge self-conversations with private conversations, to display them together.
+                    includeSelfConversations
                 )
                 .then(function(response) {
                     var conversations = response.conversations;
@@ -531,11 +553,27 @@ function(
      * @param {String} namespace Unique identifier for the Routes
      * @param {Object} root The section container element.
      * @param {Function} loadCallback The callback to load items.
-     * @param {Number} type The conversation type for this section
+     * @param {Array|null} type The conversation types for this section
      * @param {bool} includeFavourites If this section includes favourites
      */
-    var registerEventListeners = function(namespace, root, loadCallback, type, includeFavourites) {
+    var registerEventListeners = function(namespace, root, loadCallback, types, includeFavourites) {
         var listRoot = LazyLoadList.getRoot(root);
+        var conversationBelongsToThisSection = function(conversation) {
+            // Make sure the type is an int so that the index of check matches correctly.
+            var conversationType = parseInt(conversation.type, 10);
+            if (
+                // If the conversation type isn't one this section cares about then we can ignore it.
+                (types && types.indexOf(conversationType) < 0) ||
+                // If this is the favourites section and the conversation isn't a favourite then ignore it.
+                (includeFavourites && !conversation.isFavourite) ||
+                // If this section doesn't include favourites and the conversation is a favourite then ignore it.
+                (!includeFavourites && conversation.isFavourite)
+            ) {
+                return false;
+            }
+
+            return true;
+        };
 
         // Set the minimum height of the section to the height of the toggle. This
         // smooths out the collapse animation.
@@ -583,16 +621,7 @@ function(
         });
 
         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {
-            // Self-conversations could be displayed as private conversations when they are not starred. So we need to exclude
-            // them from the following check to make sure last messages are updated properly for them.
-            if (
-                (type && conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF &&
-                type != MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE && !conversation.isFavourite) ||
-                (type && conversation.type != MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF &&
-                type != conversation.type) ||
-                (includeFavourites && !conversation.isFavourite) ||
-                (!includeFavourites && conversation.isFavourite)
-            ) {
+            if (!conversationBelongsToThisSection(conversation)) {
                 return;
             }
 
@@ -621,16 +650,12 @@ function(
 
         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, function(conversation) {
             var conversationElement = null;
-            if (includeFavourites && (!type || type == conversation.type)) {
+            if (conversationBelongsToThisSection(conversation)) {
                 conversationElement = getConversationElement(root, conversation.id);
                 if (!conversationElement.length) {
                     createNewConversation(root, conversation);
                 }
-            } else if (type == conversation.type ||
-                    (type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE &&
-                     conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF)) {
-                // Self-conversations are displayed in the private conversations section, so they should be removed from
-                // there when they are favourited.
+            } else {
                 conversationElement = getConversationElement(root, conversation.id);
                 if (conversationElement.length) {
                     deleteConversation(root, conversationElement);
@@ -640,20 +665,16 @@ function(
 
         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, function(conversation) {
             var conversationElement = null;
-            if (includeFavourites) {
-                conversationElement = getConversationElement(root, conversation.id);
-                if (conversationElement.length) {
-                    deleteConversation(root, conversationElement);
-                }
-            } else if (type == conversation.type ||
-                    (type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE &&
-                     conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF)) {
-                // Self-conversations are displayed in the private conversations section, so they should be added
-                // there when they are unfavourited.
+            if (conversationBelongsToThisSection(conversation)) {
                 conversationElement = getConversationElement(root, conversation.id);
                 if (!conversationElement.length) {
                     createNewConversation(root, conversation);
                 }
+            } else {
+                conversationElement = getConversationElement(root, conversation.id);
+                if (conversationElement.length) {
+                    deleteConversation(root, conversationElement);
+                }
             }
         });
 
@@ -675,17 +696,17 @@ function(
      * @param {Object} header The header container element.
      * @param {Object} body The section container element.
      * @param {Object} footer The footer container element.
-     * @param {Number} type The conversation type for this section
+     * @param {Array} types The conversation types that show in this section
      * @param {bool} includeFavourites If this section includes favourites
      * @param {Object} totalCountPromise Resolves wth the total conversations count
      * @param {Object} unreadCountPromise Resolves wth the unread conversations count
      */
-    var show = function(namespace, header, body, footer, type, includeFavourites, totalCountPromise, unreadCountPromise) {
+    var show = function(namespace, header, body, footer, types, includeFavourites, totalCountPromise, unreadCountPromise) {
         var root = $(body);
 
         if (!root.attr('data-init')) {
-            var loadCallback = getLoadCallback(type, includeFavourites, 0);
-            registerEventListeners(namespace, root, loadCallback, type, includeFavourites);
+            var loadCallback = getLoadCallback(types, includeFavourites, 0);
+            registerEventListeners(namespace, root, loadCallback, types, includeFavourites);
 
             if (isVisible(root)) {
                 setExpanded(root);