Bug 1171940: move the context display into the chat area inside the Hello conversation window. r=Standard8
authorMike de Boer <mdeboer@mozilla.com>
Fri, 03 Jul 2015 19:00:22 +0200
changeset 275697 f338aab869d37473cf6e8dbc8be796abc858e8f4
parent 275696 078576757615b9c4841814a40d7f1d35b87524e5
child 275698 d34865c942e33075ebe127159a98ddda3c7a4d15
push id3220
push userbzhao@mozilla.com
push dateMon, 06 Jul 2015 03:31:09 +0000
reviewersStandard8
bugs1171940
milestone42.0a1
Bug 1171940: move the context display into the chat area inside the Hello conversation window. r=Standard8
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/content/shared/js/textChatStore.js
browser/components/loop/content/shared/js/textChatView.js
browser/components/loop/content/shared/js/textChatView.jsx
browser/components/loop/modules/MozLoopService.jsm
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/test/shared/textChatView_test.js
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -803,17 +803,18 @@ loop.roomViews = (function(mozL10n) {
                 error: this.state.error, 
                 mozLoop: this.props.mozLoop, 
                 roomData: roomData, 
                 savingContext: this.state.savingContext, 
                 show: !shouldRenderInvitationOverlay && shouldRenderContextView}), 
               React.createElement(sharedViews.chat.TextChatView, {
                 dispatcher: this.props.dispatcher, 
                 showAlways: false, 
-                showRoomName: false})
+                showRoomName: false, 
+                useDesktopPaths: true})
             )
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -803,17 +803,18 @@ loop.roomViews = (function(mozL10n) {
                 error={this.state.error}
                 mozLoop={this.props.mozLoop}
                 roomData={roomData}
                 savingContext={this.state.savingContext}
                 show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
               <sharedViews.chat.TextChatView
                 dispatcher={this.props.dispatcher}
                 showAlways={false}
-                showRoomName={false} />
+                showRoomName={false}
+                useDesktopPaths={true} />
             </div>
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/shared/js/textChatStore.js
+++ b/browser/components/loop/content/shared/js/textChatStore.js
@@ -95,22 +95,39 @@ loop.store.TextChatStore = (function() {
       var message = {
         type: type,
         contentType: messageData.contentType,
         message: messageData.message,
         extraData: messageData.extraData,
         sentTimestamp: messageData.sentTimestamp,
         receivedTimestamp: messageData.receivedTimestamp
       };
-      var newList = this._storeState.messageList.concat(message);
+      var newList = [].concat(this._storeState.messageList);
+      var isContext = message.contentType === CHAT_CONTENT_TYPES.CONTEXT;
+      if (isContext) {
+        var contextUpdated = false;
+        for (var i = 0, l = newList.length; i < l; ++i) {
+          // Replace the current context message with the provided update.
+          if (newList[i].contentType === CHAT_CONTENT_TYPES.CONTEXT) {
+            newList[i] = message;
+            contextUpdated = true;
+            break;
+          }
+        }
+        if (!contextUpdated) {
+          newList.push(message);
+        }
+      } else {
+        newList.push(message);
+      }
       this.setStoreState({ messageList: newList });
 
       // Notify MozLoopService if appropriate that a message has been appended
       // and it should therefore check if we need a different sized window or not.
-      if (type != CHAT_MESSAGE_TYPES.SPECIAL) {
+      if (message.contentType != CHAT_CONTENT_TYPES.ROOM_NAME) {
         window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
       }
     },
 
     /**
      * Handles received text chat messages.
      *
      * @param {sharedActions.ReceivedTextChatMessage} actionData
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -63,17 +63,18 @@ loop.shared.views.chat = (function(mozL1
       );
     }
   });
 
   var TextChatRoomName = React.createClass({displayName: "TextChatRoomName",
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
-      message: React.PropTypes.string.isRequired
+      message: React.PropTypes.string.isRequired,
+      useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     render: function() {
       return (
         React.createElement("div", {className: "text-chat-entry special room-name"}, 
           React.createElement("p", null, mozL10n.get("rooms_welcome_title", {conversationName: this.props.message}))
         )
       );
@@ -92,17 +93,18 @@ loop.shared.views.chat = (function(mozL1
     ],
 
     statics: {
       ONE_MINUTE: 60
     },
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+      messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+      useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     getInitialState: function() {
       return {
         receivedMessageCount: 0
       };
     },
 
@@ -152,28 +154,33 @@ loop.shared.views.chat = (function(mozL1
       return (
         React.createElement("div", {className: "text-chat-entries"}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
                   switch (entry.contentType) {
                     case CHAT_CONTENT_TYPES.ROOM_NAME:
-                      return React.createElement(TextChatRoomName, {key: i, message: entry.message});
+                      return (
+                        React.createElement(TextChatRoomName, {
+                          key: i, 
+                          message: entry.message, 
+                          useDesktopPaths: this.props.useDesktopPaths})
+                      );
                     case CHAT_CONTENT_TYPES.CONTEXT:
                       return (
                         React.createElement("div", {className: "context-url-view-wrapper", key: i}, 
                           React.createElement(sharedViews.ContextUrlView, {
                             allowClick: true, 
                             description: entry.message, 
                             dispatcher: this.props.dispatcher, 
                             showContextTitle: true, 
                             thumbnail: entry.extraData.thumbnail, 
                             url: entry.extraData.location, 
-                            useDesktopPaths: false})
+                            useDesktopPaths: this.props.useDesktopPaths})
                         )
                       );
                     default:
                       console.error("Unsupported contentType",
                                     entry.contentType);
                       return null;
                   }
                 }
@@ -329,69 +336,67 @@ loop.shared.views.chat = (function(mozL1
     }
   });
 
   /**
    * Displays the text chat view. This includes the text chat messages as well
    * as a field for entering new messages.
    *
    * @property {loop.Dispatcher} dispatcher
-   * @property {Boolean} showAlways         If false, the view will not be rendered
-   *                                        if text chat is not enabled and the
-   *                                        message list is empty.
-   * @property {Boolean} showRoomName       Set to true to show the room name special
-   *                                        list item.
+   * @property {Boolean}         showRoomName Set to true to show the room name
+   *                                          special list item.
    */
   var TextChatView = React.createClass({displayName: "TextChatView",
     mixins: [
       React.addons.LinkedStateMixin,
       loop.store.StoreMixin("textChatStore")
     ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      showAlways: React.PropTypes.bool.isRequired,
-      showRoomName: React.PropTypes.bool.isRequired
+      showRoomName: React.PropTypes.bool.isRequired,
+      useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     getInitialState: function() {
       return this.getStoreState();
     },
 
     render: function() {
       var messageList;
       var hasNonSpecialMessages;
 
       if (this.props.showRoomName) {
         messageList = this.state.messageList;
         hasNonSpecialMessages = messageList.some(function(item) {
           return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
         });
       } else {
-        // XXX Desktop should be showing the initial context here (bug 1171940).
         messageList = this.state.messageList.filter(function(item) {
-          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
+          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
+            item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
         });
         hasNonSpecialMessages = !!messageList.length;
       }
 
-      if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
+      if (!this.state.textChatEnabled && !messageList.length) {
         return null;
       }
 
       var textChatViewClasses = React.addons.classSet({
         "text-chat-view": true,
         "text-chat-disabled": !this.state.textChatEnabled
       });
 
       return (
         React.createElement("div", {className: textChatViewClasses}, 
           React.createElement(TextChatEntriesView, {
             dispatcher: this.props.dispatcher, 
-            messageList: messageList}), 
+            messageList: messageList, 
+            useDesktopPaths: this.props.useDesktopPaths}), 
           React.createElement(TextChatInputView, {
             dispatcher: this.props.dispatcher, 
             showPlaceholder: !hasNonSpecialMessages, 
             textChatEnabled: this.state.textChatEnabled})
         )
       );
     }
   });
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -63,17 +63,18 @@ loop.shared.views.chat = (function(mozL1
       );
     }
   });
 
   var TextChatRoomName = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
-      message: React.PropTypes.string.isRequired
+      message: React.PropTypes.string.isRequired,
+      useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     render: function() {
       return (
         <div className="text-chat-entry special room-name">
           <p>{mozL10n.get("rooms_welcome_title", {conversationName: this.props.message})}</p>
         </div>
       );
@@ -92,17 +93,18 @@ loop.shared.views.chat = (function(mozL1
     ],
 
     statics: {
       ONE_MINUTE: 60
     },
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+      messageList: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+      useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     getInitialState: function() {
       return {
         receivedMessageCount: 0
       };
     },
 
@@ -152,28 +154,33 @@ loop.shared.views.chat = (function(mozL1
       return (
         <div className="text-chat-entries">
           <div className="text-chat-scroller">
             {
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
                   switch (entry.contentType) {
                     case CHAT_CONTENT_TYPES.ROOM_NAME:
-                      return <TextChatRoomName key={i} message={entry.message}/>;
+                      return (
+                        <TextChatRoomName
+                          key={i}
+                          message={entry.message}
+                          useDesktopPaths={this.props.useDesktopPaths} />
+                      );
                     case CHAT_CONTENT_TYPES.CONTEXT:
                       return (
                         <div className="context-url-view-wrapper" key={i}>
                           <sharedViews.ContextUrlView
                             allowClick={true}
                             description={entry.message}
                             dispatcher={this.props.dispatcher}
                             showContextTitle={true}
                             thumbnail={entry.extraData.thumbnail}
                             url={entry.extraData.location}
-                            useDesktopPaths={false} />
+                            useDesktopPaths={this.props.useDesktopPaths} />
                         </div>
                       );
                     default:
                       console.error("Unsupported contentType",
                                     entry.contentType);
                       return null;
                   }
                 }
@@ -329,69 +336,67 @@ loop.shared.views.chat = (function(mozL1
     }
   });
 
   /**
    * Displays the text chat view. This includes the text chat messages as well
    * as a field for entering new messages.
    *
    * @property {loop.Dispatcher} dispatcher
-   * @property {Boolean} showAlways         If false, the view will not be rendered
-   *                                        if text chat is not enabled and the
-   *                                        message list is empty.
-   * @property {Boolean} showRoomName       Set to true to show the room name special
-   *                                        list item.
+   * @property {Boolean}         showRoomName Set to true to show the room name
+   *                                          special list item.
    */
   var TextChatView = React.createClass({
     mixins: [
       React.addons.LinkedStateMixin,
       loop.store.StoreMixin("textChatStore")
     ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      showAlways: React.PropTypes.bool.isRequired,
-      showRoomName: React.PropTypes.bool.isRequired
+      showRoomName: React.PropTypes.bool.isRequired,
+      useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     getInitialState: function() {
       return this.getStoreState();
     },
 
     render: function() {
       var messageList;
       var hasNonSpecialMessages;
 
       if (this.props.showRoomName) {
         messageList = this.state.messageList;
         hasNonSpecialMessages = messageList.some(function(item) {
           return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
         });
       } else {
-        // XXX Desktop should be showing the initial context here (bug 1171940).
         messageList = this.state.messageList.filter(function(item) {
-          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
+          return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
+            item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
         });
         hasNonSpecialMessages = !!messageList.length;
       }
 
-      if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
+      if (!this.state.textChatEnabled && !messageList.length) {
         return null;
       }
 
       var textChatViewClasses = React.addons.classSet({
         "text-chat-view": true,
         "text-chat-disabled": !this.state.textChatEnabled
       });
 
       return (
         <div className={textChatViewClasses}>
           <TextChatEntriesView
             dispatcher={this.props.dispatcher}
-            messageList={messageList} />
+            messageList={messageList}
+            useDesktopPaths={this.props.useDesktopPaths} />
           <TextChatInputView
             dispatcher={this.props.dispatcher}
             showPlaceholder={!hasNonSpecialMessages}
             textChatEnabled={this.state.textChatEnabled} />
         </div>
       );
     }
   });
--- a/browser/components/loop/modules/MozLoopService.jsm
+++ b/browser/components/loop/modules/MozLoopService.jsm
@@ -885,17 +885,20 @@ let MozLoopServiceInternal = {
           LoopChatEnabled: "loopChatEnabled",
           LoopChatMessageAppended: "loopChatMessageAppended"
         };
 
         function onChatEvent(ev) {
           // When the chat box or messages are shown, resize the panel or window
           // to be slightly higher to accomodate them.
           let customSize = kSizeMap[ev.type];
-          if (customSize) {
+          let currSize = chatbox.getAttribute("customSize");
+          // If the size is already at the requested one or at the maximum size
+          // already, don't do anything. Especially don't make it shrink.
+          if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
             chatbox.setAttribute("customSize", customSize);
             chatbox.parentNode.setAttribute("customSize", customSize);
           }
         }
 
         window.addEventListener("LoopChatEnabled", onChatEvent);
         window.addEventListener("LoopChatMessageAppended", onChatEvent);
 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -459,17 +459,18 @@ loop.standaloneRoomViews = (function(moz
                   isLoading: this._shouldRenderScreenShareLoading(), 
                   mediaType: "screen-share", 
                   posterUrl: this.props.screenSharePosterUrl, 
                   srcVideoObject: this.state.screenShareVideoObject})
               ), 
               React.createElement(sharedViews.chat.TextChatView, {
                 dispatcher: this.props.dispatcher, 
                 showAlways: true, 
-                showRoomName: true}), 
+                showRoomName: true, 
+                useDesktopPaths: false}), 
               React.createElement("div", {className: "local"}, 
                 React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
                   isLoading: this._shouldRenderLocalLoading(), 
                   mediaType: "local", 
                   posterUrl: this.props.localPosterUrl, 
                   srcVideoObject: this.state.localSrcVideoObject})
               )
             ), 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -459,17 +459,18 @@ loop.standaloneRoomViews = (function(moz
                   isLoading={this._shouldRenderScreenShareLoading()}
                   mediaType="screen-share"
                   posterUrl={this.props.screenSharePosterUrl}
                   srcVideoObject={this.state.screenShareVideoObject} />
               </div>
               <sharedViews.chat.TextChatView
                 dispatcher={this.props.dispatcher}
                 showAlways={true}
-                showRoomName={true} />
+                showRoomName={true}
+                useDesktopPaths={false} />
               <div className="local">
                 <sharedViews.MediaView displayAvatar={this.state.videoMuted}
                   isLoading={this._shouldRenderLocalLoading()}
                   mediaType="local"
                   posterUrl={this.props.localPosterUrl}
                   srcVideoObject={this.state.localSrcVideoObject} />
               </div>
             </div>
--- a/browser/components/loop/test/shared/textChatView_test.js
+++ b/browser/components/loop/test/shared/textChatView_test.js
@@ -244,17 +244,18 @@ describe("loop.shared.views.TextChatView
     });
   });
 
   describe("TextChatView", function() {
     var view;
 
     function mountTestComponent(extraProps) {
       var props = _.extend({
-        dispatcher: dispatcher
+        dispatcher: dispatcher,
+        useDesktopPaths: false
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.shared.views.chat.TextChatView, props));
     }
 
     beforeEach(function() {
       store.setStoreState({ textChatEnabled: true });
     });
@@ -283,40 +284,26 @@ describe("loop.shared.views.TextChatView
       });
 
       var node = view.getDOMNode();
 
       expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
           .to.eql(2);
     });
 
-    it("should not display anything if no messages and text chat not enabled and showAlways is false", function() {
+    it("should display the view if no messages and text chat not enabled", function() {
       store.setStoreState({ textChatEnabled: false });
 
-      view = mountTestComponent({
-        showAlways: false
-      });
-
-      expect(view.getDOMNode()).eql(null);
-    });
-
-    it("should display the view if no messages and text chat not enabled and showAlways is true", function() {
-      store.setStoreState({ textChatEnabled: false });
-
-      view = mountTestComponent({
-        showAlways: true
-      });
+      view = mountTestComponent();
 
       expect(view.getDOMNode()).not.eql(null);
     });
 
     it("should display the view if text chat is enabled", function() {
-      view = mountTestComponent({
-        showAlways: true
-      });
+      view = mountTestComponent();
 
       expect(view.getDOMNode()).not.eql(null);
     });
 
     it("should display only the text chat box if entry is enabled but there are no messages", function() {
       view = mountTestComponent();
 
       var node = view.getDOMNode();
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1238,32 +1238,32 @@
 
           React.createElement(Section, {name: "TextChatView"}, 
             React.createElement(FramedExample, {dashed: true, 
                            height: 160, 
                            summary: "TextChatView: desktop embedded", 
                            width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(TextChatView, {dispatcher: dispatcher, 
-                              showAlways: false, 
-                              showRoomName: false})
+                              showRoomName: false, 
+                              useDesktopPaths: false})
               )
             ), 
 
             React.createElement(FramedExample, {cssClass: "standalone", 
                            dashed: true, 
                            height: 400, 
                            summary: "Standalone Text Chat conversation (200x400)", 
                            width: 200}, 
               React.createElement("div", {className: "standalone text-chat-example"}, 
                 React.createElement("div", {className: "media-wrapper"}, 
                   React.createElement(TextChatView, {
                     dispatcher: dispatcher, 
-                    showAlways: true, 
-                    showRoomName: true})
+                    showRoomName: true, 
+                    useDesktopPaths: false})
                 )
               )
             )
           ), 
 
           React.createElement(Section, {className: "svg-icons", name: "SVG icons preview"}, 
             React.createElement(Example, {summary: "10x10"}, 
               React.createElement(SVGIcons, {size: "10x10"})
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1238,32 +1238,32 @@
 
           <Section name="TextChatView">
             <FramedExample dashed={true}
                            height={160}
                            summary="TextChatView: desktop embedded"
                            width={298}>
               <div className="fx-embedded">
                 <TextChatView dispatcher={dispatcher}
-                              showAlways={false}
-                              showRoomName={false} />
+                              showRoomName={false}
+                              useDesktopPaths={false} />
               </div>
             </FramedExample>
 
             <FramedExample cssClass="standalone"
                            dashed={true}
                            height={400}
                            summary="Standalone Text Chat conversation (200x400)"
                            width={200}>
               <div className="standalone text-chat-example">
                 <div className="media-wrapper">
                   <TextChatView
                     dispatcher={dispatcher}
-                    showAlways={true}
-                    showRoomName={true} />
+                    showRoomName={true}
+                    useDesktopPaths={false} />
                 </div>
               </div>
             </FramedExample>
           </Section>
 
           <Section className="svg-icons" name="SVG icons preview">
             <Example summary="10x10">
               <SVGIcons size="10x10"/>