diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 35f133398c9..efa0affd1d8 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1302,6 +1302,7 @@ pref("devtools.toolbox.splitconsoleHeight", 100); // Toolbox Button preferences pref("devtools.command-button-pick.enabled", true); +pref("devtools.command-button-frames.enabled", false); pref("devtools.command-button-splitconsole.enabled", true); pref("devtools.command-button-paintflashing.enabled", false); pref("devtools.command-button-tilt.enabled", false); @@ -1531,10 +1532,10 @@ pref("browser.newtabpage.rows", 3); pref("browser.newtabpage.columns", 5); // directory tiles download URL -pref("browser.newtabpage.directory.source", "https://tiles.up.mozillalabs.com/v2/links/fetch"); +pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v2/links/fetch"); // endpoint to send newtab click and view pings -pref("browser.newtabpage.directory.ping", "https://tiles.up.mozillalabs.com/v2/links/"); +pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v2/links/"); // Enable the DOM fullscreen API. pref("full-screen-api.enabled", true); diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js index a8ae8c1de52..cf41b643f63 100644 --- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -110,62 +110,53 @@ SocialUI = { }, observe: function SocialUI_observe(subject, topic, data) { - // Exceptions here sometimes don't get reported properly, report them - // manually :( - try { - switch (topic) { - case "social:provider-enabled": - SocialMarks.populateToolbarPalette(); - SocialStatus.populateToolbarPalette(); - break; - case "social:provider-disabled": - SocialMarks.removeProvider(data); - SocialStatus.removeProvider(data); - SocialSidebar.disableProvider(data); - break; - case "social:provider-reload": - SocialStatus.reloadProvider(data); - // if the reloaded provider is our current provider, fall through - // to social:providers-changed so the ui will be reset - if (!SocialSidebar.provider || SocialSidebar.provider.origin != data) - return; - // currently only the sidebar and flyout have a selected provider. - // sidebar provider has changed (possibly to null), ensure the content - // is unloaded and the frames are reset, they will be loaded in - // providers-changed below if necessary. - SocialSidebar.unloadSidebar(); - SocialFlyout.unload(); - // fall through to providers-changed to ensure the reloaded provider - // is correctly reflected in any UI and the multi-provider menu - case "social:providers-changed": - this._providersChanged(); - break; - - // Provider-specific notifications - case "social:ambient-notification-changed": - SocialStatus.updateButton(data); - break; - case "social:profile-changed": - // make sure anything that happens here only affects the provider for - // which the profile is changing, and that anything we call actually - // needs to change based on profile data. - SocialStatus.updateButton(data); - break; - case "social:frameworker-error": - if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) { - SocialSidebar.setSidebarErrorMessage(); - } - break; - - case "nsPref:changed": - if (data == "social.toast-notifications.enabled") { - SocialSidebar.updateToggleNotifications(); - } - break; - } - } catch (e) { - Components.utils.reportError(e + "\n" + e.stack); - throw e; + switch (topic) { + case "social:provider-enabled": + SocialMarks.populateToolbarPalette(); + SocialStatus.populateToolbarPalette(); + break; + case "social:provider-disabled": + SocialMarks.removeProvider(data); + SocialStatus.removeProvider(data); + SocialSidebar.disableProvider(data); + break; + case "social:provider-reload": + SocialStatus.reloadProvider(data); + // if the reloaded provider is our current provider, fall through + // to social:providers-changed so the ui will be reset + if (!SocialSidebar.provider || SocialSidebar.provider.origin != data) + return; + // currently only the sidebar and flyout have a selected provider. + // sidebar provider has changed (possibly to null), ensure the content + // is unloaded and the frames are reset, they will be loaded in + // providers-changed below if necessary. + SocialSidebar.unloadSidebar(); + SocialFlyout.unload(); + // fall through to providers-changed to ensure the reloaded provider + // is correctly reflected in any UI and the multi-provider menu + case "social:providers-changed": + this._providersChanged(); + break; + // Provider-specific notifications + case "social:ambient-notification-changed": + SocialStatus.updateButton(data); + break; + case "social:profile-changed": + // make sure anything that happens here only affects the provider for + // which the profile is changing, and that anything we call actually + // needs to change based on profile data. + SocialStatus.updateButton(data); + break; + case "social:frameworker-error": + if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) { + SocialSidebar.setSidebarErrorMessage(); + } + break; + case "nsPref:changed": + if (data == "social.toast-notifications.enabled") { + SocialSidebar.updateToggleNotifications(); + } + break; } }, diff --git a/browser/components/loop/content/conversation.html b/browser/components/loop/content/conversation.html index 113e735e059..f88c44375fe 100644 --- a/browser/components/loop/content/conversation.html +++ b/browser/components/loop/content/conversation.html @@ -7,10 +7,11 @@ + - +
diff --git a/browser/components/loop/content/js/conversation.js b/browser/components/loop/content/js/conversation.js index b46f8f0c454..4481b7b8856 100644 --- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -77,13 +77,10 @@ loop.conversation = (function(OT, mozL10n) { render: function() { /* jshint ignore:start */ - var btnClassAccept = "btn btn-success btn-accept call-audio-video"; - var btnClassBlock = "btn btn-error btn-block"; + var btnClassAccept = "btn btn-accept"; var btnClassDecline = "btn btn-error btn-decline"; - var conversationPanelClass = "incoming-call " + - loop.shared.utils.getTargetPlatform(); - var cx = React.addons.classSet; - var dropdownMenuClassesDecline = cx({ + var conversationPanelClass = "incoming-call"; + var dropdownMenuClassesDecline = React.addons.classSet({ "native-dropdown-menu": true, "conversation-window-dropdown": true, "visually-hidden": !this.state.showDeclineMenu @@ -91,10 +88,13 @@ loop.conversation = (function(OT, mozL10n) { return ( React.DOM.div({className: conversationPanelClass}, React.DOM.h2(null, __("incoming_call")), - React.DOM.div({className: "button-group incoming-call-action-group"}, - React.DOM.div({className: "button-chevron-menu-group"}, - React.DOM.div({className: "button-group-chevron"}, - React.DOM.div({className: "button-group"}, + React.DOM.div({className: "btn-group incoming-call-action-group"}, + + React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}), + + React.DOM.div({className: "btn-chevron-menu-group"}, + React.DOM.div({className: "btn-group-chevron"}, + React.DOM.div({className: "btn-group"}, React.DOM.button({className: btnClassDecline, onClick: this._handleDecline}, @@ -114,18 +114,27 @@ loop.conversation = (function(OT, mozL10n) { ) ), - React.DOM.div({className: "button-chevron-menu-group"}, - React.DOM.div({className: "button-group"}, + React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}), + + React.DOM.div({className: "btn-chevron-menu-group"}, + React.DOM.div({className: "btn-group"}, React.DOM.button({className: btnClassAccept, onClick: this._handleAccept("audio-video")}, - __("incoming_call_answer_button") + React.DOM.span({className: "fx-embedded-answer-btn-text"}, + __("incoming_call_answer_button") + ), + React.DOM.span({className: "fx-embedded-btn-icon-video"} + ) ), React.DOM.div({className: "call-audio-only", onClick: this._handleAccept("audio"), title: __("incoming_call_answer_audio_only_tooltip")} ) ) - ) + ), + + React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}) + ) ) ); @@ -339,6 +348,8 @@ loop.conversation = (function(OT, mozL10n) { document.title = mozL10n.get("incoming_call_title"); + document.body.classList.add(loop.shared.utils.getTargetPlatform()); + var client = new loop.Client(); router = new ConversationRouter({ client: client, diff --git a/browser/components/loop/content/js/conversation.jsx b/browser/components/loop/content/js/conversation.jsx index 26c1ef63e39..e8944ef4659 100644 --- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -77,13 +77,10 @@ loop.conversation = (function(OT, mozL10n) { render: function() { /* jshint ignore:start */ - var btnClassAccept = "btn btn-success btn-accept call-audio-video"; - var btnClassBlock = "btn btn-error btn-block"; + var btnClassAccept = "btn btn-accept"; var btnClassDecline = "btn btn-error btn-decline"; - var conversationPanelClass = "incoming-call " + - loop.shared.utils.getTargetPlatform(); - var cx = React.addons.classSet; - var dropdownMenuClassesDecline = cx({ + var conversationPanelClass = "incoming-call"; + var dropdownMenuClassesDecline = React.addons.classSet({ "native-dropdown-menu": true, "conversation-window-dropdown": true, "visually-hidden": !this.state.showDeclineMenu @@ -91,10 +88,13 @@ loop.conversation = (function(OT, mozL10n) { return (

{__("incoming_call")}

-
-
-
-
+
+ +
+ +
+
+
-
-
+
+ +
+
+ +
+
); @@ -339,6 +348,8 @@ loop.conversation = (function(OT, mozL10n) { document.title = mozL10n.get("incoming_call_title"); + document.body.classList.add(loop.shared.utils.getTargetPlatform()); + var client = new loop.Client(); router = new ConversationRouter({ client: client, diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index fd5a1bd7572..028c0ea0d54 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -77,9 +77,9 @@ loop.panel = (function(_, mozL10n) { __("display_name_available_status"); return ( - React.DOM.div({className: "footer component-spacer"}, + React.DOM.div({className: "footer"}, React.DOM.div({className: "do-not-disturb"}, - React.DOM.p({className: "dnd-status", onClick: this.showDropdownMenu}, + React.DOM.div({className: "dnd-status", onClick: this.showDropdownMenu}, React.DOM.span(null, availabilityText), React.DOM.i({className: availabilityStatus}) ), @@ -138,10 +138,8 @@ loop.panel = (function(_, mozL10n) { render: function() { return ( - React.DOM.div({className: "component-spacer share generate-url"}, - React.DOM.div({className: "description"}, - React.DOM.p({className: "description-content"}, this.props.summary) - ), + React.DOM.div({className: "share generate-url"}, + React.DOM.div({className: "description"}, this.props.summary), React.DOM.div({className: "action"}, this.props.children ) @@ -175,6 +173,12 @@ loop.panel = (function(_, mozL10n) { }, componentDidMount: function() { + // If we've already got a callURL, don't bother requesting a new one. + // As of this writing, only used for visual testing in the UI showcase. + if (this.state.callUrl.length) { + return; + } + this.setState({pending: true}); this.props.client.requestCallUrl(this.conversationIdentifier(), this._onCallUrlReceived); @@ -239,7 +243,7 @@ loop.panel = (function(_, mozL10n) { React.DOM.div({className: "invite"}, React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true", className: inputCSSClass}), - React.DOM.p({className: "button-group url-actions"}, + React.DOM.p({className: "btn-group url-actions"}, React.DOM.button({className: "btn btn-email", disabled: !this.state.callUrl, onClick: this.handleEmailButtonClick, 'data-mailto': this._generateMailTo()}, @@ -361,6 +365,8 @@ loop.panel = (function(_, mozL10n) { }); Backbone.history.start(); + document.body.classList.add(loop.shared.utils.getTargetPlatform()); + // Notify the window that we've finished initalization and initial layout var evtObject = document.createEvent('Event'); evtObject.initEvent('loopPanelInitialized', true, false); diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index 0a46e367ece..0185b9bf9ca 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -77,12 +77,12 @@ loop.panel = (function(_, mozL10n) { __("display_name_available_status"); return ( -
+
-

+

{availabilityText} -

+
  • -
    -

    {this.props.summary}

    -
    +
    +
    {this.props.summary}
    {this.props.children}
    @@ -175,6 +173,12 @@ loop.panel = (function(_, mozL10n) { }, componentDidMount: function() { + // If we've already got a callURL, don't bother requesting a new one. + // As of this writing, only used for visual testing in the UI showcase. + if (this.state.callUrl.length) { + return; + } + this.setState({pending: true}); this.props.client.requestCallUrl(this.conversationIdentifier(), this._onCallUrlReceived); @@ -239,7 +243,7 @@ loop.panel = (function(_, mozL10n) {
    -

    +

  • -
  • -
  • +
  • + +
  • +
  • + +
  • +
  • + +
); /* jshint ignore:end */ @@ -347,16 +353,18 @@ loop.shared.views = (function(_, OT, l10n) { render: function() { /* jshint ignore:start */ return ( -
- -
-
-
+
+
+ +
+
+
+
+
-
); diff --git a/browser/components/loop/jar.mn b/browser/components/loop/jar.mn index 84cce38f75a..04808608185 100644 --- a/browser/components/loop/jar.mn +++ b/browser/components/loop/jar.mn @@ -17,6 +17,7 @@ browser.jar: content/browser/loop/js/panel.js (content/js/panel.js) # Shared styles + content/browser/loop/shared/css/reset.css (content/shared/css/reset.css) content/browser/loop/shared/css/common.css (content/shared/css/common.css) content/browser/loop/shared/css/panel.css (content/shared/css/panel.css) content/browser/loop/shared/css/conversation.css (content/shared/css/conversation.css) diff --git a/browser/components/loop/standalone/content/css/webapp.css b/browser/components/loop/standalone/content/css/webapp.css index df297d9a8e8..cb3695f224c 100644 --- a/browser/components/loop/standalone/content/css/webapp.css +++ b/browser/components/loop/standalone/content/css/webapp.css @@ -10,50 +10,49 @@ body, height: 100%; } -body { +.standalone { width: 100%; - /* prevent the video convsersation elements to occupy the whole available - width hence the height while keeping aspect ratio */ - max-width: 730px; - margin: 0 auto; background: #fbfbfb; color: #666; text-align: center; font-family: Open Sans,sans-serif; } -header { +.standalone-header { border-radius: 4px; background: #fff; padding: 1rem 5rem; border: 1px solid #E7E7E7; box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.03); + margin-top: 2rem; } /* * Top/Bottom spacing **/ -header { - margin-top: 2rem; -} - -.footer { +.standalone-footer { margin-bottom: 2rem; } .container { display: flex; - align-items: center; flex-direction: column; - justify-content: space-between; + margin: 0 auto; + + /* prevent the video conversation elements to occupy the whole available + width hence the height while keeping aspect ratio */ + width: 30%; + min-width: 400px; height: 100%; + align-items: center; + justify-content: space-between; } .container-box { display: flex; flex-direction: column; - align-content: center; width: 100%; + align-content: center; } .footer, @@ -96,12 +95,6 @@ header { background-repeat: no-repeat; } -.call-url { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - .loop-logo { width: 100px; height: 100px; @@ -111,12 +104,17 @@ header { background-repeat: no-repeat; } -.large-font { +.standalone-header-title, +.standalone-call-btn-label { + font-weight: lighter; +} + +.standalone-header-title { font-size: 1.8rem; } -.light-weight-font { - font-weight: lighter; +.standalone-call-btn-label { + font-size: 1.2rem; } .light-color-font { @@ -124,28 +122,49 @@ header { font-weight: normal; } +.standalone-btn-chevron-menu-group { + display: flex; + justify-content: space-between; + flex: 2; +} + .start-audio-only-call, -.start-audio-video-call { - background-color: none; - background-image: url("../shared/img/audio-default-16x16@1.5x.png"); - background-position: 80% center; - background-size: 10px; +.standalone-call-btn-video-icon { + width: 1.2rem; + height: 1.2rem; background-repeat: no-repeat; cursor: pointer; } .start-audio-only-call { - border: none; width: 100%; + border: none; + background-size: 10px; + background-color: #F0F0F0; + background-image: url("../shared/img/audio-default-16x16@1.5x.png"); + background-position: 90% center; } .start-audio-only-call:hover { background-image: url("../shared/img/audio-inverse-14x14.png"); } -.start-audio-video-call { - background-size: 20px; +.standalone-call-btn-video-icon { background-image: url("../shared/img/video-inverse-14x14.png"); + display: inline-block; + background-size: 1.2rem; + margin-left: .5rem; +} + +/* Ellipsed text content of audio-video call btn */ +.standalone-call-btn-text { + /* make some room for the video icon */ + max-width: 85%; + vertical-align: bottom; +} + +.standalone-call-btn-video-icon { + vertical-align: top; } @media (min-resolution: 2dppx) { @@ -155,8 +174,29 @@ header { .start-audio-only-call:hover { background-image: url("../shared/img/audio-inverse-14x14@2x.png"); } - .start-audio-video-call { + .standalone-call-btn-video-icon { background-image: url("../shared/img/video-inverse-14x14@2x.png"); } } +.btn-large { + /* Dimensions from spec + * https://people.mozilla.org/~dhenein/labs/loop-link-spec/#call-start */ + font-size: 1rem; + padding: .3em .5rem; +} + + .btn-large + .btn-chevron { + padding: 1rem; + height: 100%; /* match full height of button */ + } + +/* + * Left / Right padding elements + * used to center components + * */ +.flex-padding-1 { + display: flex; + flex: 1; +} + diff --git a/browser/components/loop/standalone/content/index.html b/browser/components/loop/standalone/content/index.html index c65b0ab3a39..0b768214043 100644 --- a/browser/components/loop/standalone/content/index.html +++ b/browser/components/loop/standalone/content/index.html @@ -6,12 +6,13 @@ Loop + - +
diff --git a/browser/components/loop/standalone/content/js/webapp.js b/browser/components/loop/standalone/content/js/webapp.js index b0c6129b7b7..643d19c6731 100644 --- a/browser/components/loop/standalone/content/js/webapp.js +++ b/browser/components/loop/standalone/content/js/webapp.js @@ -48,7 +48,7 @@ loop.webapp = (function($, _, OT, webL10n) { React.DOM.div({className: "promote-firefox"}, React.DOM.h3(null, __("promote_firefox_hello_heading")), React.DOM.p(null, - React.DOM.a({className: "btn btn-large btn-success", + React.DOM.a({className: "btn btn-large btn-accept", href: "https://www.mozilla.org/firefox/"}, __("get_firefox_button") ) @@ -100,8 +100,8 @@ loop.webapp = (function($, _, OT, webL10n) { return ( /* jshint ignore:start */ - React.DOM.header({className: "container-box"}, - React.DOM.h1({className: "light-weight-font"}, + React.DOM.header({className: "standalone-header container-box"}, + React.DOM.h1({className: "standalone-header-title"}, React.DOM.strong(null, __("brandShortname")), " ", __("clientShortname") ), React.DOM.div({className: "loop-logo", title: "Firefox WebRTC! logo"}), @@ -120,7 +120,7 @@ loop.webapp = (function($, _, OT, webL10n) { var ConversationFooter = React.createClass({displayName: 'ConversationFooter', render: function() { return ( - React.DOM.div({className: "footer container-box"}, + React.DOM.div({className: "standalone-footer container-box"}, React.DOM.div({title: "Mozilla Logo", className: "footer-logo"}) ) ); @@ -229,8 +229,7 @@ loop.webapp = (function($, _, OT, webL10n) { "https://www.mozilla.org/privacy/'>" + privacy_notice_name + "" }); - var btnClassStartCall = "btn btn-large btn-success " + - "start-audio-video-call " + + var btnClassStartCall = "btn btn-large btn-accept " + loop.shared.utils.getTargetPlatform(); var dropdownMenuClasses = React.addons.classSet({ "native-dropdown-large-parent": true, @@ -250,23 +249,26 @@ loop.webapp = (function($, _, OT, webL10n) { ConversationHeader({ urlCreationDateString: this.state.urlCreationDateString}), - React.DOM.p({className: "large-font light-weight-font"}, + React.DOM.p({className: "standalone-call-btn-label"}, __("initiate_call_button_label") ), React.DOM.div({id: "messages"}), - React.DOM.div({className: "button-group"}, + React.DOM.div({className: "btn-group"}, React.DOM.div({className: "flex-padding-1"}), - React.DOM.div({className: "button-chevron-menu-group"}, - React.DOM.div({className: "button-group-chevron"}, - React.DOM.div({className: "button-group"}, + React.DOM.div({className: "standalone-btn-chevron-menu-group"}, + React.DOM.div({className: "btn-group-chevron"}, + React.DOM.div({className: "btn-group"}, React.DOM.button({className: btnClassStartCall, onClick: this._initiateOutgoingCall("audio-video"), disabled: this.state.disableCallButton, title: __("initiate_audio_video_call_tooltip")}, - __("initiate_audio_video_call_button") + React.DOM.span({className: "standalone-call-btn-text"}, + __("initiate_audio_video_call_button") + ), + React.DOM.span({className: "standalone-call-btn-video-icon"}) ), React.DOM.div({className: "btn-chevron", @@ -537,8 +539,8 @@ loop.webapp = (function($, _, OT, webL10n) { var helper = new WebappHelper(); var client = new loop.StandaloneClient({ baseServerUrl: baseServerUrl - }), - router = new WebappRouter({ + }); + var router = new WebappRouter({ helper: helper, notifier: new sharedViews.NotificationListView({el: "#messages"}), client: client, @@ -547,12 +549,16 @@ loop.webapp = (function($, _, OT, webL10n) { pendingCallTimeout: loop.config.pendingCallTimeout }) }); + Backbone.history.start(); if (helper.isIOS(navigator.platform)) { router.navigate("unsupportedDevice", {trigger: true}); } else if (!OT.checkSystemRequirements()) { router.navigate("unsupportedBrowser", {trigger: true}); } + + document.body.classList.add(loop.shared.utils.getTargetPlatform()); + // Set the 'lang' and 'dir' attributes to when the page is translated document.documentElement.lang = document.webL10n.getLanguage(); document.documentElement.dir = document.webL10n.getDirection(); diff --git a/browser/components/loop/standalone/content/js/webapp.jsx b/browser/components/loop/standalone/content/js/webapp.jsx index 15338378ff1..ef3bfaf8bb8 100644 --- a/browser/components/loop/standalone/content/js/webapp.jsx +++ b/browser/components/loop/standalone/content/js/webapp.jsx @@ -48,7 +48,7 @@ loop.webapp = (function($, _, OT, webL10n) {

{__("promote_firefox_hello_heading")}

- {__("get_firefox_button")} @@ -100,8 +100,8 @@ loop.webapp = (function($, _, OT, webL10n) { return ( /* jshint ignore:start */ -

-

+
+

{__("brandShortname")} {__("clientShortname")}

@@ -120,7 +120,7 @@ loop.webapp = (function($, _, OT, webL10n) { var ConversationFooter = React.createClass({ render: function() { return ( -
+
); @@ -229,8 +229,7 @@ loop.webapp = (function($, _, OT, webL10n) { "https://www.mozilla.org/privacy/'>" + privacy_notice_name + "" }); - var btnClassStartCall = "btn btn-large btn-success " + - "start-audio-video-call " + + var btnClassStartCall = "btn btn-large btn-accept " + loop.shared.utils.getTargetPlatform(); var dropdownMenuClasses = React.addons.classSet({ "native-dropdown-large-parent": true, @@ -250,23 +249,26 @@ loop.webapp = (function($, _, OT, webL10n) { -

+

{__("initiate_call_button_label")}

-
+
-
-
-
+
+
+
when the page is translated document.documentElement.lang = document.webL10n.getLanguage(); document.documentElement.dir = document.webL10n.getDirection(); diff --git a/browser/components/loop/standalone/content/l10n/data.ini b/browser/components/loop/standalone/content/l10n/data.ini index bcf40fdb5cf..fc5965ad995 100644 --- a/browser/components/loop/standalone/content/l10n/data.ini +++ b/browser/components/loop/standalone/content/l10n/data.ini @@ -7,6 +7,7 @@ network_disconnected=The network connection terminated abruptly. peer_ended_conversation2=The person you were calling has ended the conversation. unable_retrieve_call_info=Unable to retrieve conversation information. hangup_button_title=Hang up +hangup_button_caption=End Call mute_local_audio_button_title=Mute your audio unmute_local_audio_button_title=Unmute your audio mute_local_video_button_title=Mute your video @@ -44,6 +45,7 @@ missing_conversation_info=Informations de communication manquantes. network_disconnected=La connexion réseau semble avoir été interrompue. unable_retrieve_call_info=Impossible de récupérer les informations liées à cet appel. hangup_button_title=Terminer l'appel +hangup_button_caption=Raccrocher mute_local_audio_button_title=Couper la diffusion audio unmute_local_audio_button_title=Reprendre la diffusion audio mute_local_video_button_title=Couper la diffusion vidéo diff --git a/browser/components/loop/standalone/server.js b/browser/components/loop/standalone/server.js index 19f7c6b8d46..1c26148a42b 100644 --- a/browser/components/loop/standalone/server.js +++ b/browser/components/loop/standalone/server.js @@ -25,6 +25,8 @@ app.get('/content/config.js', function (req, res) { app.use('/', express.static(__dirname + '/../')); // This lets /content/ be mapped right for the static contents. app.use('/', express.static(__dirname + '/')); +// This lets standalone components load images into the UI showcase +app.use('/standalone/content', express.static(__dirname + '/../content')); var server = app.listen(port); diff --git a/browser/components/loop/test/desktop-local/conversation_test.js b/browser/components/loop/test/desktop-local/conversation_test.js index 59bae9493ba..6fb8a3785aa 100644 --- a/browser/components/loop/test/desktop-local/conversation_test.js +++ b/browser/components/loop/test/desktop-local/conversation_test.js @@ -547,27 +547,38 @@ describe("loop.conversation", function() { }); describe("click event on .btn-accept", function() { - it("should trigger an 'accept' conversation model event", function() { + it("should trigger an 'accept' conversation model event", function () { var buttonAccept = view.getDOMNode().querySelector(".btn-accept"); - + model.trigger.withArgs("accept"); TestUtils.Simulate.click(buttonAccept); /* Setting a model property triggers 2 events */ - sinon.assert.calledThrice(model.trigger); - sinon.assert.calledWith(model.trigger, "accept"); - sinon.assert.calledWith(model.trigger, "change:selectedCallType"); - sinon.assert.calledWith(model.trigger, "change"); + sinon.assert.calledOnce(model.trigger.withArgs("accept")); }); - it("should set selectedCallType to audio-video", function() { - var buttonAccept = view.getDOMNode().querySelector(".call-audio-video"); + it("should set selectedCallType to audio-video", function () { + var buttonAccept = view.getDOMNode().querySelector(".btn-accept"); sandbox.stub(model, "set"); TestUtils.Simulate.click(buttonAccept); sinon.assert.calledOnce(model.set); - sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video"); + sinon.assert.calledWithExactly(model.set, "selectedCallType", + "audio-video"); }); + }); + + describe("click event on .call-audio-only", function() { + + it("should trigger an 'accept' conversation model event", function () { + var buttonAccept = view.getDOMNode().querySelector(".call-audio-only"); + model.trigger.withArgs("accept"); + TestUtils.Simulate.click(buttonAccept); + + /* Setting a model property triggers 2 events */ + sinon.assert.calledOnce(model.trigger.withArgs("accept")); + }); + it("should set selectedCallType to audio", function() { var buttonAccept = view.getDOMNode().querySelector(".call-audio-only"); diff --git a/browser/components/loop/test/functional/test_1_browser_call.py b/browser/components/loop/test/functional/test_1_browser_call.py index 849c4728254..da140688413 100644 --- a/browser/components/loop/test/functional/test_1_browser_call.py +++ b/browser/components/loop/test/functional/test_1_browser_call.py @@ -39,7 +39,7 @@ class Test1BrowserCall(MarionetteTestCase): .until(lambda m: m.find_element(by, locator).is_displayed()) return self.marionette.find_element(by, locator) - # XXX workaround for Marionette bug YYY + # XXX workaround for Marionette bug 1055309 def wait_for_element_exists(self, by, locator, timeout=None): Wait(self.marionette, timeout, ignored_exceptions=[NoSuchElementException, StaleElementException]) \ @@ -95,7 +95,8 @@ class Test1BrowserCall(MarionetteTestCase): def start_and_verify_outgoing_call(self): # make the call! - call_button = self.marionette.find_element(By.CLASS_NAME, "btn-success") + call_button = self.marionette.find_element(By.CLASS_NAME, + "btn-accept") call_button.click() # expect a video container on standalone side @@ -116,7 +117,7 @@ class Test1BrowserCall(MarionetteTestCase): # Accept the incoming call call_button = self.marionette.find_element(By.CLASS_NAME, - "btn-success") + "btn-accept") # accept call from the desktop side call_button.click() diff --git a/browser/components/loop/test/standalone/webapp_test.js b/browser/components/loop/test/standalone/webapp_test.js index fd234c37bd7..a87402ee87c 100644 --- a/browser/components/loop/test/standalone/webapp_test.js +++ b/browser/components/loop/test/standalone/webapp_test.js @@ -528,27 +528,29 @@ describe("loop.webapp", function() { ); }); - it("should start the conversation establishment process", function() { - var button = view.getDOMNode().querySelector(".start-audio-video-call"); - React.addons.TestUtils.Simulate.click(button); + it("should start the audio-video conversation establishment process", + function() { + var button = view.getDOMNode().querySelector(".btn-accept"); + React.addons.TestUtils.Simulate.click(button); - sinon.assert.calledOnce(setupOutgoingCall); - sinon.assert.calledWithExactly(setupOutgoingCall); + sinon.assert.calledOnce(setupOutgoingCall); + sinon.assert.calledWithExactly(setupOutgoingCall); }); - it("should start the conversation establishment process", function() { - var button = view.getDOMNode().querySelector(".start-audio-only-call"); - React.addons.TestUtils.Simulate.click(button); + it("should start the audio-only conversation establishment process", + function() { + var button = view.getDOMNode().querySelector(".start-audio-only-call"); + React.addons.TestUtils.Simulate.click(button); - sinon.assert.calledOnce(setupOutgoingCall); - sinon.assert.calledWithExactly(setupOutgoingCall); - }); + sinon.assert.calledOnce(setupOutgoingCall); + sinon.assert.calledWithExactly(setupOutgoingCall); + }); it("should disable audio-video button once session is initiated", function() { conversation.set("loopToken", "fake"); - var button = view.getDOMNode().querySelector(".start-audio-video-call"); + var button = view.getDOMNode().querySelector(".btn-accept"); React.addons.TestUtils.Simulate.click(button); expect(button.disabled).to.eql(true); @@ -576,7 +578,7 @@ describe("loop.webapp", function() { it("should set selectedCallType to audio-video", function() { conversation.set("loopToken", "fake"); - var button = view.getDOMNode().querySelector(".start-audio-video-call"); + var button = view.getDOMNode().querySelector(".standalone-call-btn-video-icon"); React.addons.TestUtils.Simulate.click(button); expect(conversation.get("selectedCallType")).to.eql("audio-video"); diff --git a/browser/components/loop/ui/fake-l10n.js b/browser/components/loop/ui/fake-l10n.js index 571c874d131..c567bf407a5 100644 --- a/browser/components/loop/ui/fake-l10n.js +++ b/browser/components/loop/ui/fake-l10n.js @@ -9,7 +9,14 @@ * @type {Object} */ document.webL10n = document.mozL10n = { - get: function(sringId, vars) { - return "" + sringId + (vars ? ";" + JSON.stringify(vars) : ""); + get: function(stringId, vars) { + + // upcase the first letter + var readableStringId = stringId.replace(/^./, function(match) { + "use strict"; + return match.toUpperCase(); + }).replace(/_/g, " "); // and convert _ chars to spaces + + return "" + readableStringId + (vars ? ";" + JSON.stringify(vars) : ""); } }; diff --git a/browser/components/loop/ui/index.html b/browser/components/loop/ui/index.html index 834e193ee91..d44740243f4 100644 --- a/browser/components/loop/ui/index.html +++ b/browser/components/loop/ui/index.html @@ -6,9 +6,11 @@ Loop UI Components Showcase + + diff --git a/browser/components/loop/ui/sample-img/video-screen-local.png b/browser/components/loop/ui/sample-img/video-screen-local.png new file mode 100644 index 00000000000..468a842acdf Binary files /dev/null and b/browser/components/loop/ui/sample-img/video-screen-local.png differ diff --git a/browser/components/loop/ui/sample-img/video-screen-remote.png b/browser/components/loop/ui/sample-img/video-screen-remote.png new file mode 100644 index 00000000000..94709d2e8f5 Binary files /dev/null and b/browser/components/loop/ui/sample-img/video-screen-remote.png differ diff --git a/browser/components/loop/ui/ui-showcase.css b/browser/components/loop/ui/ui-showcase.css index b2ee4ad3b6e..93c93125677 100644 --- a/browser/components/loop/ui/ui-showcase.css +++ b/browser/components/loop/ui/ui-showcase.css @@ -16,8 +16,16 @@ padding-bottom: 1em; } -.showcase .menu > a { +.showcase > header > h1, +.showcase > section > h1 { + font-size: 2em; + font-weight: bold; + margin: .5em 0; +} + +.showcase-menu > a { margin-right: .5em; + padding: .4rem; } .showcase > section { @@ -43,7 +51,10 @@ } .showcase > section .example > h3 { + font-size: 1.2em; + font-weight: bold; border-bottom: 1px dashed #aaa; + margin: .5em 0; } .showcase p.note { @@ -52,3 +63,22 @@ color: #666; font-style: italic; } + +.showcase p.note > strong { + font-weight: bold; +} + +/* Images as fake videos for conversation view */ +.conversation .media.nested .remote, +.conversation .media.nested .local { + background-size: contain; +} + +.conversation .media.nested .remote { + background-image: url("sample-img/video-screen-remote.png"); + background-repeat: no-repeat; +} + +.conversation .media.nested .local { + background-image: url("sample-img/video-screen-local.png"); +} diff --git a/browser/components/loop/ui/ui-showcase.js b/browser/components/loop/ui/ui-showcase.js index 0f78d6e6dd8..613f9ed3a89 100644 --- a/browser/components/loop/ui/ui-showcase.js +++ b/browser/components/loop/ui/ui-showcase.js @@ -17,7 +17,8 @@ var IncomingCallView = loop.conversation.IncomingCallView; // 2. Standalone webapp - var CallUrlExpiredView = loop.webapp.CallUrlExpiredView; + var CallUrlExpiredView = loop.webapp.CallUrlExpiredView; + var StartConversationView = loop.webapp.StartConversationView; // 3. Shared components var ConversationToolbar = loop.shared.views.ConversationToolbar; @@ -38,7 +39,14 @@ var stageFeedbackApiClient = new loop.FeedbackAPIClient( "https://input.allizom.org/api/v1/feedback", { product: "Loop" - }); + } + ); + + var mockClient = { + requestCallUrl: function() {} + }; + + var mockConversationModel = new loop.shared.models.ConversationModel({}, {sdk: {}}); var Example = React.createClass({displayName: 'Example', render: function() { @@ -72,7 +80,7 @@ React.DOM.div({className: "showcase"}, React.DOM.header(null, React.DOM.h1(null, "Loop UI Components Showcase"), - React.DOM.nav({className: "menu"}, + React.DOM.nav({className: "showcase-menu"}, React.Children.map(this.props.children, function(section) { return ( React.DOM.a({className: "btn btn-info", href: "#" + section.props.name}, @@ -96,11 +104,11 @@ React.DOM.p({className: "note"}, React.DOM.strong(null, "Note:"), " 332px wide." ), - Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}}, - PanelView(null) - ), Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}}, - PanelView({callUrl: "http://invalid.example.url/"}) + PanelView({callUrl: "http://invalid.example.url/", client: mockClient}) + ), + Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}}, + PanelView({client: mockClient}) ) ), @@ -111,20 +119,58 @@ ), Section({name: "ConversationToolbar"}, - Example({summary: "Default"}, - ConversationToolbar({video: {enabled: true}, audio: {enabled: true}}) + React.DOM.h3(null, "Desktop Conversation Window"), + React.DOM.div({className: "conversation-window"}, + Example({summary: "Default (260x265)", dashed: "true"}, + ConversationToolbar({video: {enabled: true}, audio: {enabled: true}}) + ), + Example({summary: "Video muted"}, + ConversationToolbar({video: {enabled: false}, audio: {enabled: true}}) + ), + Example({summary: "Audio muted"}, + ConversationToolbar({video: {enabled: true}, audio: {enabled: false}}) + ) ), - Example({summary: "Video muted"}, - ConversationToolbar({video: {enabled: false}, audio: {enabled: true}}) - ), - Example({summary: "Audio muted"}, - ConversationToolbar({video: {enabled: true}, audio: {enabled: false}}) + + React.DOM.h3(null, "Standalone"), + React.DOM.div({className: "standalone"}, + Example({summary: "Default"}, + ConversationToolbar({video: {enabled: true}, audio: {enabled: true}}) + ), + Example({summary: "Video muted"}, + ConversationToolbar({video: {enabled: false}, audio: {enabled: true}}) + ), + Example({summary: "Audio muted"}, + ConversationToolbar({video: {enabled: true}, audio: {enabled: false}}) + ) ) ), + Section({name: "StartConversationView"}, + + Example({summary: "Start conversation view", dashed: "true"}, + React.DOM.div({className: "standalone"}, + StartConversationView({model: mockConversationModel, + client: mockClient}) + ) + ) + + ), + Section({name: "ConversationView"}, - Example({summary: "Default"}, - ConversationView({video: {enabled: true}, audio: {enabled: true}}) + + Example({summary: "Desktop conversation window", dashed: "true", + style: {width: "260px", height: "265px"}}, + React.DOM.div({className: "conversation-window"}, + ConversationView({video: {enabled: true}, audio: {enabled: true}, + model: mockConversationModel}) + ) + ), + Example({summary: "Standalone version"}, + React.DOM.div({className: "standalone"}, + ConversationView({video: {enabled: true}, audio: {enabled: true}, + model: mockConversationModel}) + ) ) ), @@ -158,6 +204,8 @@ }); window.addEventListener("DOMContentLoaded", function() { + var body = document.body; + body.className = loop.shared.utils.getTargetPlatform(); React.renderComponent(App(null), document.body); }); })(); diff --git a/browser/components/loop/ui/ui-showcase.jsx b/browser/components/loop/ui/ui-showcase.jsx index d7859a19c3d..d83e1e25447 100644 --- a/browser/components/loop/ui/ui-showcase.jsx +++ b/browser/components/loop/ui/ui-showcase.jsx @@ -17,7 +17,8 @@ var IncomingCallView = loop.conversation.IncomingCallView; // 2. Standalone webapp - var CallUrlExpiredView = loop.webapp.CallUrlExpiredView; + var CallUrlExpiredView = loop.webapp.CallUrlExpiredView; + var StartConversationView = loop.webapp.StartConversationView; // 3. Shared components var ConversationToolbar = loop.shared.views.ConversationToolbar; @@ -38,7 +39,14 @@ var stageFeedbackApiClient = new loop.FeedbackAPIClient( "https://input.allizom.org/api/v1/feedback", { product: "Loop" - }); + } + ); + + var mockClient = { + requestCallUrl: function() {} + }; + + var mockConversationModel = new loop.shared.models.ConversationModel({}, {sdk: {}}); var Example = React.createClass({ render: function() { @@ -72,7 +80,7 @@

Loop UI Components Showcase

-