Bug 1074686 - Part 3 Revamped view architecture for Desktop Loop rooms. r=Standard8 a=loop-only
authorNicolas Perriault <nperriault@gmail.com>
Tue, 11 Nov 2014 09:33:05 +0000
changeset 226139 fcc39c3f52532de8c8d2f85fd13031e14875e108
parent 226138 b2276210d8b80dbbe4856535350d09936ac1de91
child 226140 18a2ba5eacbb086343a1bc3cb799fb5138bafe8e
push id7271
push userrjesup@wgate.com
push dateSun, 16 Nov 2014 09:56:03 +0000
treeherdermozilla-aurora@d8f242cbe2a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, loop-only
bugs1074686
milestone35.0a2
Bug 1074686 - Part 3 Revamped view architecture for Desktop Loop rooms. r=Standard8 a=loop-only
browser/components/loop/content/js/conversation.js
browser/components/loop/content/js/conversation.jsx
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/test/desktop-local/conversation_test.js
browser/components/loop/test/desktop-local/roomViews_test.js
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -13,17 +13,17 @@ loop.conversation = (function(mozL10n) {
 
   var sharedViews = loop.shared.views;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedActions = loop.shared.actions;
 
   var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
   var CallIdentifierView = loop.conversationViews.CallIdentifierView;
-  var DesktopRoomControllerView = loop.roomViews.DesktopRoomControllerView;
+  var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
     mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
 
     propTypes: {
       model: React.PropTypes.object.isRequired,
       video: React.PropTypes.bool.isRequired
     },
@@ -579,18 +579,17 @@ loop.conversation = (function(mozL10n) {
         }
         case "outgoing": {
           return (OutgoingConversationView({
             store: this.props.conversationStore, 
             dispatcher: this.props.dispatcher}
           ));
         }
         case "room": {
-          return (DesktopRoomControllerView({
-            mozLoop: navigator.mozLoop, 
+          return (DesktopRoomConversationView({
             dispatcher: this.props.dispatcher, 
             roomStore: this.props.roomStore}
           ));
         }
         case "failed": {
           return (GenericFailureView({
             cancelCall: this.closeWindow}
           ));
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -13,17 +13,17 @@ loop.conversation = (function(mozL10n) {
 
   var sharedViews = loop.shared.views;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedActions = loop.shared.actions;
 
   var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
   var CallIdentifierView = loop.conversationViews.CallIdentifierView;
-  var DesktopRoomControllerView = loop.roomViews.DesktopRoomControllerView;
+  var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   var IncomingCallView = React.createClass({
     mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
 
     propTypes: {
       model: React.PropTypes.object.isRequired,
       video: React.PropTypes.bool.isRequired
     },
@@ -579,18 +579,17 @@ loop.conversation = (function(mozL10n) {
         }
         case "outgoing": {
           return (<OutgoingConversationView
             store={this.props.conversationStore}
             dispatcher={this.props.dispatcher}
           />);
         }
         case "room": {
-          return (<DesktopRoomControllerView
-            mozLoop={navigator.mozLoop}
+          return (<DesktopRoomConversationView
             dispatcher={this.props.dispatcher}
             roomStore={this.props.roomStore}
           />);
         }
         case "failed": {
           return (<GenericFailureView
             cancelCall={this.closeWindow}
           />);
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -36,17 +36,21 @@ loop.roomViews = (function(mozL10n) {
       this.stopListening(this.props.roomStore);
     },
 
     _onActiveRoomStateChanged: function() {
       this.setState(this.props.roomStore.getStoreState("activeRoom"));
     },
 
     getInitialState: function() {
-      return this.props.roomStore.getStoreState("activeRoom");
+      var storeState = this.props.roomStore.getStoreState("activeRoom");
+      return _.extend(storeState, {
+        // Used by the UI showcase.
+        roomState: this.props.roomState || storeState.roomState
+      });
     }
   };
 
   /**
    * Desktop room invitation view (overlay).
    */
   var DesktopRoomInvitationView = React.createClass({displayName: 'DesktopRoomInvitationView',
     mixins: [ActiveRoomStoreMixin],
@@ -67,140 +71,108 @@ loop.roomViews = (function(mozL10n) {
 
     handleCopyButtonClick: function(event) {
       event.preventDefault();
       // XXX
     },
 
     render: function() {
       return (
-        React.DOM.div({className: "room-conversation-wrapper"}, 
-          React.DOM.div({className: "room-invitation-overlay"}, 
-            React.DOM.form({onSubmit: this.handleFormSubmit}, 
-              React.DOM.input({type: "text", ref: "roomName", 
-                placeholder: mozL10n.get("rooms_name_this_room_label")})
+        React.DOM.div({className: "room-invitation-overlay"}, 
+          React.DOM.form({onSubmit: this.handleFormSubmit}, 
+            React.DOM.input({type: "text", ref: "roomName", 
+              placeholder: mozL10n.get("rooms_name_this_room_label")})
+          ), 
+          React.DOM.p(null, mozL10n.get("invite_header_text")), 
+          React.DOM.div({className: "btn-group call-action-group"}, 
+            React.DOM.button({className: "btn btn-info btn-email", 
+                    onClick: this.handleEmailButtonClick}, 
+              mozL10n.get("share_button2")
             ), 
-            React.DOM.p(null, mozL10n.get("invite_header_text")), 
-            React.DOM.div({className: "btn-group call-action-group"}, 
-              React.DOM.button({className: "btn btn-info btn-email", 
-                      onClick: this.handleEmailButtonClick}, 
-                mozL10n.get("share_button2")
-              ), 
-              React.DOM.button({className: "btn btn-info btn-copy", 
-                      onClick: this.handleCopyButtonClick}, 
-                mozL10n.get("copy_url_button2")
-              )
+            React.DOM.button({className: "btn btn-info btn-copy", 
+                    onClick: this.handleCopyButtonClick}, 
+              mozL10n.get("copy_url_button2")
             )
-          ), 
-          DesktopRoomConversationView({roomStore: this.props.roomStore})
+          )
         )
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
   var DesktopRoomConversationView = React.createClass({displayName: 'DesktopRoomConversationView',
-    mixins: [ActiveRoomStoreMixin],
+    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       video: React.PropTypes.object,
-      audio: React.PropTypes.object,
-      displayInvitation: React.PropTypes.bool
+      audio: React.PropTypes.object
     },
 
     getDefaultProps: function() {
       return {
         video: {enabled: true, visible: true},
         audio: {enabled: true, visible: true}
       };
     },
 
-    render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": !this.props.video.enabled
-      });
-      return (
-        React.DOM.div({className: "room-conversation-wrapper"}, 
-          React.DOM.div({className: "video-layout-wrapper"}, 
-            React.DOM.div({className: "conversation room-conversation"}, 
-              React.DOM.div({className: "media nested"}, 
-                React.DOM.div({className: "video_wrapper remote_wrapper"}, 
-                  React.DOM.div({className: "video_inner remote"})
-                ), 
-                React.DOM.div({className: localStreamClasses})
-              ), 
-              sharedViews.ConversationToolbar({
-                video: this.props.video, 
-                audio: this.props.audio, 
-                publishStream: noop, 
-                hangup: noop})
-            )
-          )
-        )
-      );
-    }
-  });
-
-  /**
-   * Desktop room controller view.
-   */
-  var DesktopRoomControllerView = React.createClass({displayName: 'DesktopRoomControllerView',
-    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
-
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
-    },
-
-    closeWindow: function() {
-      window.close();
-    },
-
-    _renderRoomView: function(roomState) {
-      switch (roomState) {
-        case ROOM_STATES.FAILED: {
-          return loop.conversation.GenericFailureView({
-            cancelCall: this.closeWindow}
-          );
-        }
-        case ROOM_STATES.INIT:
-        case ROOM_STATES.GATHER:
-        case ROOM_STATES.READY:
-        case ROOM_STATES.JOINED: {
-          return DesktopRoomInvitationView({
-            dispatcher: this.props.dispatcher, 
-            roomStore: this.props.roomStore}
-          );
-        }
-        // XXX needs bug 1074686/1074702
-        case ROOM_STATES.HAS_PARTICIPANTS: {
-          return DesktopRoomConversationView({
-            dispatcher: this.props.dispatcher, 
-            roomStore: this.props.roomStore}
-          );
-        }
+    _renderInvitationOverlay: function() {
+      if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
+        return DesktopRoomInvitationView({
+          roomStore: this.props.roomStore, 
+          dispatcher: this.props.dispatcher}
+        );
       }
+      return null;
     },
 
     render: function() {
       if (this.state.roomName) {
         this.setTitle(this.state.roomName);
       }
-      return (
-        React.DOM.div({className: "room-conversation-wrapper"}, 
-          this._renderRoomView(this.state.roomState)
-        )
-      );
+
+      var localStreamClasses = React.addons.classSet({
+        local: true,
+        "local-stream": true,
+        "local-stream-audio": !this.props.video.enabled
+      });
+
+      switch(this.state.roomState) {
+        case ROOM_STATES.FAILED: {
+          return loop.conversation.GenericFailureView({
+            cancelCall: this.closeWindow}
+          );
+        }
+        default: {
+          return (
+            React.DOM.div({className: "room-conversation-wrapper"}, 
+              this._renderInvitationOverlay(), 
+              React.DOM.div({className: "video-layout-wrapper"}, 
+                React.DOM.div({className: "conversation room-conversation"}, 
+                  React.DOM.div({className: "media nested"}, 
+                    React.DOM.div({className: "video_wrapper remote_wrapper"}, 
+                      React.DOM.div({className: "video_inner remote"})
+                    ), 
+                    React.DOM.div({className: localStreamClasses})
+                  ), 
+                  sharedViews.ConversationToolbar({
+                    video: this.props.video, 
+                    audio: this.props.audio, 
+                    publishStream: noop, 
+                    hangup: noop})
+                )
+              )
+            )
+          );
+        }
+      }
     }
   });
 
   return {
     ActiveRoomStoreMixin: ActiveRoomStoreMixin,
-    DesktopRoomControllerView: DesktopRoomControllerView,
     DesktopRoomConversationView: DesktopRoomConversationView,
     DesktopRoomInvitationView: DesktopRoomInvitationView
   };
 
 })(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -36,17 +36,21 @@ loop.roomViews = (function(mozL10n) {
       this.stopListening(this.props.roomStore);
     },
 
     _onActiveRoomStateChanged: function() {
       this.setState(this.props.roomStore.getStoreState("activeRoom"));
     },
 
     getInitialState: function() {
-      return this.props.roomStore.getStoreState("activeRoom");
+      var storeState = this.props.roomStore.getStoreState("activeRoom");
+      return _.extend(storeState, {
+        // Used by the UI showcase.
+        roomState: this.props.roomState || storeState.roomState
+      });
     }
   };
 
   /**
    * Desktop room invitation view (overlay).
    */
   var DesktopRoomInvitationView = React.createClass({
     mixins: [ActiveRoomStoreMixin],
@@ -67,140 +71,108 @@ loop.roomViews = (function(mozL10n) {
 
     handleCopyButtonClick: function(event) {
       event.preventDefault();
       // XXX
     },
 
     render: function() {
       return (
-        <div className="room-conversation-wrapper">
-          <div className="room-invitation-overlay">
-            <form onSubmit={this.handleFormSubmit}>
-              <input type="text" ref="roomName"
-                placeholder={mozL10n.get("rooms_name_this_room_label")} />
-            </form>
-            <p>{mozL10n.get("invite_header_text")}</p>
-            <div className="btn-group call-action-group">
-              <button className="btn btn-info btn-email"
-                      onClick={this.handleEmailButtonClick}>
-                {mozL10n.get("share_button2")}
-              </button>
-              <button className="btn btn-info btn-copy"
-                      onClick={this.handleCopyButtonClick}>
-                {mozL10n.get("copy_url_button2")}
-              </button>
-            </div>
+        <div className="room-invitation-overlay">
+          <form onSubmit={this.handleFormSubmit}>
+            <input type="text" ref="roomName"
+              placeholder={mozL10n.get("rooms_name_this_room_label")} />
+          </form>
+          <p>{mozL10n.get("invite_header_text")}</p>
+          <div className="btn-group call-action-group">
+            <button className="btn btn-info btn-email"
+                    onClick={this.handleEmailButtonClick}>
+              {mozL10n.get("share_button2")}
+            </button>
+            <button className="btn btn-info btn-copy"
+                    onClick={this.handleCopyButtonClick}>
+              {mozL10n.get("copy_url_button2")}
+            </button>
           </div>
-          <DesktopRoomConversationView roomStore={this.props.roomStore} />
         </div>
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
   var DesktopRoomConversationView = React.createClass({
-    mixins: [ActiveRoomStoreMixin],
+    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       video: React.PropTypes.object,
-      audio: React.PropTypes.object,
-      displayInvitation: React.PropTypes.bool
+      audio: React.PropTypes.object
     },
 
     getDefaultProps: function() {
       return {
         video: {enabled: true, visible: true},
         audio: {enabled: true, visible: true}
       };
     },
 
-    render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": !this.props.video.enabled
-      });
-      return (
-        <div className="room-conversation-wrapper">
-          <div className="video-layout-wrapper">
-            <div className="conversation room-conversation">
-              <div className="media nested">
-                <div className="video_wrapper remote_wrapper">
-                  <div className="video_inner remote"></div>
-                </div>
-                <div className={localStreamClasses}></div>
-              </div>
-              <sharedViews.ConversationToolbar
-                video={this.props.video}
-                audio={this.props.audio}
-                publishStream={noop}
-                hangup={noop} />
-            </div>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  /**
-   * Desktop room controller view.
-   */
-  var DesktopRoomControllerView = React.createClass({
-    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
-
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
-    },
-
-    closeWindow: function() {
-      window.close();
-    },
-
-    _renderRoomView: function(roomState) {
-      switch (roomState) {
-        case ROOM_STATES.FAILED: {
-          return <loop.conversation.GenericFailureView
-            cancelCall={this.closeWindow}
-          />;
-        }
-        case ROOM_STATES.INIT:
-        case ROOM_STATES.GATHER:
-        case ROOM_STATES.READY:
-        case ROOM_STATES.JOINED: {
-          return <DesktopRoomInvitationView
-            dispatcher={this.props.dispatcher}
-            roomStore={this.props.roomStore}
-          />;
-        }
-        // XXX needs bug 1074686/1074702
-        case ROOM_STATES.HAS_PARTICIPANTS: {
-          return <DesktopRoomConversationView
-            dispatcher={this.props.dispatcher}
-            roomStore={this.props.roomStore}
-          />;
-        }
+    _renderInvitationOverlay: function() {
+      if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
+        return <DesktopRoomInvitationView
+          roomStore={this.props.roomStore}
+          dispatcher={this.props.dispatcher}
+        />;
       }
+      return null;
     },
 
     render: function() {
       if (this.state.roomName) {
         this.setTitle(this.state.roomName);
       }
-      return (
-        <div className="room-conversation-wrapper">{
-          this._renderRoomView(this.state.roomState)
-        }</div>
-      );
+
+      var localStreamClasses = React.addons.classSet({
+        local: true,
+        "local-stream": true,
+        "local-stream-audio": !this.props.video.enabled
+      });
+
+      switch(this.state.roomState) {
+        case ROOM_STATES.FAILED: {
+          return <loop.conversation.GenericFailureView
+            cancelCall={this.closeWindow}
+          />;
+        }
+        default: {
+          return (
+            <div className="room-conversation-wrapper">
+              {this._renderInvitationOverlay()}
+              <div className="video-layout-wrapper">
+                <div className="conversation room-conversation">
+                  <div className="media nested">
+                    <div className="video_wrapper remote_wrapper">
+                      <div className="video_inner remote"></div>
+                    </div>
+                    <div className={localStreamClasses}></div>
+                  </div>
+                  <sharedViews.ConversationToolbar
+                    video={this.props.video}
+                    audio={this.props.audio}
+                    publishStream={noop}
+                    hangup={noop} />
+                </div>
+              </div>
+            </div>
+          );
+        }
+      }
     }
   });
 
   return {
     ActiveRoomStoreMixin: ActiveRoomStoreMixin,
-    DesktopRoomControllerView: DesktopRoomControllerView,
     DesktopRoomConversationView: DesktopRoomConversationView,
     DesktopRoomInvitationView: DesktopRoomInvitationView
   };
 
 })(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -209,17 +209,17 @@ describe("loop.conversation", function()
     });
 
     it("should display the RoomView for rooms", function() {
       conversationAppStore.setStoreState({windowType: "room"});
 
       ccView = mountTestComponent();
 
       TestUtils.findRenderedComponentWithType(ccView,
-        loop.roomViews.DesktopRoomControllerView);
+        loop.roomViews.DesktopRoomConversationView);
     });
 
     it("should display the GenericFailureView for failures", function() {
       conversationAppStore.setStoreState({windowType: "failed"});
 
       ccView = mountTestComponent();
 
       TestUtils.findRenderedComponentWithType(ccView,
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -76,23 +76,23 @@ describe("loop.roomViews", function () {
       }));
 
       activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
 
       expect(testView.state).eql({roomState: ROOM_STATES.READY});
     });
   });
 
-  describe("DesktopRoomControllerView", function() {
+  describe("DesktopRoomConversationView", function() {
     var view;
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
-        new loop.roomViews.DesktopRoomControllerView({
-          mozLoop: {},
+        new loop.roomViews.DesktopRoomConversationView({
+          dispatcher: dispatcher,
           roomStore: roomStore
         }));
     }
 
     describe("#render", function() {
       it("should set document.title to store.serverData.roomName", function() {
         mountTestComponent();
 
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -17,33 +17,35 @@
   // 1. Desktop components
   // 1.1 Panel
   var PanelView = loop.panel.PanelView;
   // 1.2. Conversation Window
   var IncomingCallView = loop.conversation.IncomingCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
-  var DesktopRoomInvitationView = loop.roomViews.DesktopRoomInvitationView;
 
   // 2. Standalone webapp
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
   var CallUrlExpiredView      = loop.webapp.CallUrlExpiredView;
   var PendingConversationView = loop.webapp.PendingConversationView;
   var StartConversationView   = loop.webapp.StartConversationView;
   var FailedConversationView  = loop.webapp.FailedConversationView;
   var EndedConversationView   = loop.webapp.EndedConversationView;
 
   // 3. Shared components
   var ConversationToolbar = loop.shared.views.ConversationToolbar;
   var ConversationView = loop.shared.views.ConversationView;
   var FeedbackView = loop.shared.views.FeedbackView;
 
+  // Room constants
+  var ROOM_STATES = loop.store.ROOM_STATES;
+
   // Local helpers
   function returnTrue() {
     return true;
   }
 
   function returnFalse() {
     return false;
   }
@@ -528,30 +530,34 @@
           Section({name: "UnsupportedDeviceView"}, 
             Example({summary: "Standalone Unsupported Device"}, 
               React.DOM.div({className: "standalone"}, 
                 UnsupportedDeviceView(null)
               )
             )
           ), 
 
-          Section({name: "DesktopRoomInvitationView"}, 
-            Example({summary: "Desktop room invitation", dashed: "true", 
+          Section({name: "DesktopRoomConversationView"}, 
+            Example({summary: "Desktop room conversation (invitation)", dashed: "true", 
                      style: {width: "260px", height: "265px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
-                DesktopRoomInvitationView({roomStore: roomStore})
+                DesktopRoomConversationView({
+                  roomStore: roomStore, 
+                  dispatcher: dispatcher, 
+                  roomState: ROOM_STATES.INIT})
               )
-            )
-          ), 
+            ), 
 
-          Section({name: "DesktopRoomConversationView"}, 
             Example({summary: "Desktop room conversation", dashed: "true", 
                      style: {width: "260px", height: "265px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
-                DesktopRoomConversationView({roomStore: roomStore})
+                DesktopRoomConversationView({
+                  roomStore: roomStore, 
+                  dispatcher: dispatcher, 
+                  roomState: ROOM_STATES.HAS_PARTICIPANTS})
               )
             )
           ), 
 
           Section({name: "SVG icons preview"}, 
             Example({summary: "16x16"}, 
               SVGIcons(null)
             )
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -17,33 +17,35 @@
   // 1. Desktop components
   // 1.1 Panel
   var PanelView = loop.panel.PanelView;
   // 1.2. Conversation Window
   var IncomingCallView = loop.conversation.IncomingCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
-  var DesktopRoomInvitationView = loop.roomViews.DesktopRoomInvitationView;
 
   // 2. Standalone webapp
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
   var CallUrlExpiredView      = loop.webapp.CallUrlExpiredView;
   var PendingConversationView = loop.webapp.PendingConversationView;
   var StartConversationView   = loop.webapp.StartConversationView;
   var FailedConversationView  = loop.webapp.FailedConversationView;
   var EndedConversationView   = loop.webapp.EndedConversationView;
 
   // 3. Shared components
   var ConversationToolbar = loop.shared.views.ConversationToolbar;
   var ConversationView = loop.shared.views.ConversationView;
   var FeedbackView = loop.shared.views.FeedbackView;
 
+  // Room constants
+  var ROOM_STATES = loop.store.ROOM_STATES;
+
   // Local helpers
   function returnTrue() {
     return true;
   }
 
   function returnFalse() {
     return false;
   }
@@ -528,30 +530,34 @@
           <Section name="UnsupportedDeviceView">
             <Example summary="Standalone Unsupported Device">
               <div className="standalone">
                 <UnsupportedDeviceView />
               </div>
             </Example>
           </Section>
 
-          <Section name="DesktopRoomInvitationView">
-            <Example summary="Desktop room invitation" dashed="true"
+          <Section name="DesktopRoomConversationView">
+            <Example summary="Desktop room conversation (invitation)" dashed="true"
                      style={{width: "260px", height: "265px"}}>
               <div className="fx-embedded">
-                <DesktopRoomInvitationView roomStore={roomStore} />
+                <DesktopRoomConversationView
+                  roomStore={roomStore}
+                  dispatcher={dispatcher}
+                  roomState={ROOM_STATES.INIT} />
               </div>
             </Example>
-          </Section>
 
-          <Section name="DesktopRoomConversationView">
             <Example summary="Desktop room conversation" dashed="true"
                      style={{width: "260px", height: "265px"}}>
               <div className="fx-embedded">
-                <DesktopRoomConversationView roomStore={roomStore} />
+                <DesktopRoomConversationView
+                  roomStore={roomStore}
+                  dispatcher={dispatcher}
+                  roomState={ROOM_STATES.HAS_PARTICIPANTS} />
               </div>
             </Example>
           </Section>
 
           <Section name="SVG icons preview">
             <Example summary="16x16">
               <SVGIcons />
             </Example>