/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ var loop = loop || {}; loop.panel = (function(_, mozL10n) { "use strict"; var sharedViews = loop.shared.views; var sharedModels = loop.shared.models; var sharedMixins = loop.shared.mixins; var sharedActions = loop.shared.actions; var Button = sharedViews.Button; var Checkbox = sharedViews.Checkbox; /** * Availability drop down menu subview. */ var AvailabilityDropdown = React.createClass({ mixins: [sharedMixins.DropdownMenuMixin()], getInitialState: function() { return { doNotDisturb: navigator.mozLoop.doNotDisturb }; }, // XXX target event can either be the li, the span or the i tag // this makes it easier to figure out the target by making a // closure with the desired status already passed in. changeAvailability: function(newAvailabilty) { return function(event) { // Note: side effect! switch (newAvailabilty) { case "available": this.setState({doNotDisturb: false}); navigator.mozLoop.doNotDisturb = false; break; case "do-not-disturb": this.setState({doNotDisturb: true}); navigator.mozLoop.doNotDisturb = true; break; } this.hideDropdownMenu(); }.bind(this); }, render: function() { var cx = React.addons.classSet; var availabilityDropdown = cx({ "dropdown-menu": true, "hide": !this.state.showMenu }); var statusIcon = cx({ "status-unavailable": this.state.doNotDisturb, "status-available": !this.state.doNotDisturb }); var availabilityText = this.state.doNotDisturb ? mozL10n.get("display_name_dnd_status") : mozL10n.get("display_name_available_status"); return (

{availabilityText}

); } }); var GettingStartedView = React.createClass({ mixins: [sharedMixins.WindowCloseMixin], propTypes: { mozLoop: React.PropTypes.object.isRequired }, handleButtonClick: function() { navigator.mozLoop.openGettingStartedTour("getting-started"); navigator.mozLoop.setLoopPref("gettingStarted.seen", true); var event = new CustomEvent("GettingStartedSeen"); window.dispatchEvent(event); this.closeWindow(); }, render: function() { if (this.props.mozLoop.getLoopPref("gettingStarted.seen")) { return null; } return (
{mozL10n.get("first_time_experience_subheading")}
); } }); /** * Displays a view requesting the user to sign-in again. */ var SignInRequestView = React.createClass({ mixins: [sharedMixins.WindowCloseMixin], propTypes: { mozLoop: React.PropTypes.object.isRequired }, handleSignInClick: function(event) { event.preventDefault(); this.props.mozLoop.logInToFxA(true); this.closeWindow(); }, handleGuestClick: function(event) { this.props.mozLoop.logOutFromFxA(); }, render: function() { var shortname = mozL10n.get("clientShortname2"); var line1 = mozL10n.get("sign_in_again_title_line_one", { clientShortname2: shortname }); var line2 = mozL10n.get("sign_in_again_title_line_two2", { clientShortname2: shortname }); var useGuestString = mozL10n.get("sign_in_again_use_as_guest_button2", { clientSuperShortname: mozL10n.get("clientSuperShortname") }); return (

{line1}

{line2}

{useGuestString}
); } }); var ToSView = React.createClass({ mixins: [sharedMixins.WindowCloseMixin], propTypes: { mozLoop: React.PropTypes.object.isRequired }, handleLinkClick: function(event) { if (!event.target || !event.target.href) { return; } event.preventDefault(); this.props.mozLoop.openURL(event.target.href); this.closeWindow(); }, render: function() { var locale = mozL10n.getLanguage(); var terms_of_use_url = this.props.mozLoop.getLoopPref("legal.ToS_url"); var privacy_notice_url = this.props.mozLoop.getLoopPref("legal.privacy_url"); var tosHTML = mozL10n.get("legal_text_and_links3", { "clientShortname": mozL10n.get("clientShortname2"), "terms_of_use": React.renderToStaticMarkup( {mozL10n.get("legal_text_tos")} ), "privacy_notice": React.renderToStaticMarkup( {mozL10n.get("legal_text_privacy")} ) }); return (

{mozL10n.get("powered_by_beforeLogo")}

); } }); /** * Panel settings (gear) menu entry. */ var SettingsDropdownEntry = React.createClass({ propTypes: { displayed: React.PropTypes.bool, extraCSSClass: React.PropTypes.string, label: React.PropTypes.string.isRequired, onClick: React.PropTypes.func.isRequired }, getDefaultProps: function() { return {displayed: true}; }, render: function() { var cx = React.addons.classSet; if (!this.props.displayed) { return null; } var extraCSSClass = { "dropdown-menu-item": true }; if (this.props.extraCSSClass) { extraCSSClass[this.props.extraCSSClass] = true; } return (
  • {this.props.label}
  • ); } }); /** * Panel settings (gear) menu. */ var SettingsDropdown = React.createClass({ propTypes: { mozLoop: React.PropTypes.object.isRequired }, mixins: [sharedMixins.DropdownMenuMixin(), sharedMixins.WindowCloseMixin], handleClickSettingsEntry: function() { // XXX to be implemented at the same time as unhiding the entry }, handleClickAccountEntry: function() { this.props.mozLoop.openFxASettings(); this.closeWindow(); }, handleClickAuthEntry: function() { if (this._isSignedIn()) { this.props.mozLoop.logOutFromFxA(); } else { this.props.mozLoop.logInToFxA(); } }, handleHelpEntry: function(event) { event.preventDefault(); var helloSupportUrl = this.props.mozLoop.getLoopPref("support_url"); this.props.mozLoop.openURL(helloSupportUrl); this.closeWindow(); }, _isSignedIn: function() { return !!this.props.mozLoop.userProfile; }, openGettingStartedTour: function() { this.props.mozLoop.openGettingStartedTour("settings-menu"); this.closeWindow(); }, render: function() { var cx = React.addons.classSet; var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" : "entry-settings-signin"; return (
    ); } }); /** * FxA sign in/up link component. */ var AccountLink = React.createClass({ mixins: [sharedMixins.WindowCloseMixin], propTypes: { fxAEnabled: React.PropTypes.bool.isRequired, userProfile: userProfileValidator }, handleSignInLinkClick: function() { navigator.mozLoop.logInToFxA(); this.closeWindow(); }, render: function() { if (!this.props.fxAEnabled) { return null; } if (this.props.userProfile && this.props.userProfile.email) { return (
    {loop.shared.utils.truncate(this.props.userProfile.email, 24)}
    ); } return (

    {mozL10n.get("panel_footer_signin_or_signup_link")}

    ); } }); var RoomEntryContextItem = React.createClass({ mixins: [loop.shared.mixins.WindowCloseMixin], propTypes: { mozLoop: React.PropTypes.object.isRequired, roomUrls: React.PropTypes.array }, handleClick: function(event) { event.stopPropagation(); event.preventDefault(); this.props.mozLoop.openURL(event.currentTarget.href); this.closeWindow(); }, render: function() { var roomUrl = this.props.roomUrls && this.props.roomUrls[0]; if (!roomUrl) { return null; } return (
    ); } }); /** * Room list entry. */ var RoomEntry = React.createClass({ propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, mozLoop: React.PropTypes.object.isRequired, room: React.PropTypes.instanceOf(loop.store.Room).isRequired }, mixins: [ loop.shared.mixins.WindowCloseMixin, sharedMixins.DropdownMenuMixin() ], getInitialState: function() { return { eventPosY: 0 }; }, _isActive: function() { return this.props.room.participants.length > 0; }, handleClickEntry: function(event) { event.preventDefault(); this.props.dispatcher.dispatch(new sharedActions.OpenRoom({ roomToken: this.props.room.roomToken })); this.closeWindow(); }, handleContextChevronClick: function(e) { e.preventDefault(); e.stopPropagation(); this.setState({ eventPosY: e.pageY }); this.toggleDropdownMenu(); }, /** * Callback called when moving cursor away from the conversation entry. * Will close the dropdown menu. */ _handleMouseOut: function() { if (this.state.showMenu) { this.toggleDropdownMenu(); } }, render: function() { var roomClasses = React.addons.classSet({ "room-entry": true, "room-active": this._isActive() }); return (

    {this.props.room.decryptedContext.roomName}

    ); } }); /** * Buttons corresponding to each conversation entry. * This component renders the video icon call button and chevron button for * displaying contextual dropdown menu for conversation entries. * It also holds the dropdown menu. */ var RoomEntryContextButtons = React.createClass({ propTypes: { dispatcher: React.PropTypes.object.isRequired, eventPosY: React.PropTypes.number.isRequired, handleClickEntry: React.PropTypes.func.isRequired, handleContextChevronClick: React.PropTypes.func.isRequired, room: React.PropTypes.object.isRequired, showMenu: React.PropTypes.bool.isRequired, toggleDropdownMenu: React.PropTypes.func.isRequired }, handleEmailButtonClick: function(event) { event.preventDefault(); event.stopPropagation(); this.props.dispatcher.dispatch( new sharedActions.EmailRoomUrl({ roomUrl: this.props.room.roomUrl, from: "panel" }) ); this.props.toggleDropdownMenu(); }, handleCopyButtonClick: function(event) { event.stopPropagation(); event.preventDefault(); this.props.dispatcher.dispatch(new sharedActions.CopyRoomUrl({ roomUrl: this.props.room.roomUrl, from: "panel" })); this.props.toggleDropdownMenu(); }, handleDeleteButtonClick: function(event) { event.stopPropagation(); event.preventDefault(); this.props.dispatcher.dispatch(new sharedActions.DeleteRoom({ roomToken: this.props.room.roomToken })); this.props.toggleDropdownMenu(); }, render: function() { return (
    ); } }); /** * Panel view. */ var PanelView = React.createClass({ propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, mozLoop: React.PropTypes.object.isRequired, notifications: React.PropTypes.object.isRequired, roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired }, getInitialState: function() { return { hasEncryptionKey: this.props.mozLoop.hasEncryptionKey, userProfile: this.props.mozLoop.userProfile, gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen") }; }, _serviceErrorToShow: function() { if (!this.props.mozLoop.errors || !Object.keys(this.props.mozLoop.errors).length) { return null; } // Just get the first error for now since more than one should be rare. var firstErrorKey = Object.keys(this.props.mozLoop.errors)[0]; return { type: firstErrorKey, error: this.props.mozLoop.errors[firstErrorKey] }; }, updateServiceErrors: function() { var serviceError = this._serviceErrorToShow(); if (serviceError) { this.props.notifications.set({ id: "service-error", level: "error", message: serviceError.error.friendlyMessage, details: serviceError.error.friendlyDetails, detailsButtonLabel: serviceError.error.friendlyDetailsButtonLabel, detailsButtonCallback: serviceError.error.friendlyDetailsButtonCallback }); } else { this.props.notifications.remove(this.props.notifications.get("service-error")); } }, _onStatusChanged: function() { var profile = this.props.mozLoop.userProfile; var currUid = this.state.userProfile ? this.state.userProfile.uid : null; var newUid = profile ? profile.uid : null; if (currUid === newUid) { // Update the state of hasEncryptionKey as this might have changed now. this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey}); } else { this.setState({userProfile: profile}); } this.updateServiceErrors(); }, _gettingStartedSeen: function() { this.setState({ gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen") }); }, componentWillMount: function() { this.updateServiceErrors(); }, componentDidMount: function() { window.addEventListener("LoopStatusChanged", this._onStatusChanged); window.addEventListener("GettingStartedSeen", this._gettingStartedSeen); }, componentWillUnmount: function() { window.removeEventListener("LoopStatusChanged", this._onStatusChanged); window.removeEventListener("GettingStartedSeen", this._gettingStartedSeen); }, render: function() { var NotificationListView = sharedViews.NotificationListView; if (!this.state.gettingStartedSeen) { return (
    ); } if (!this.state.hasEncryptionKey) { return ; } return (
    ); } }); /** * Panel initialisation. */ function init() { // Do the initial L10n setup, we do this before anything // else to ensure the L10n environment is setup correctly. mozL10n.initialize(navigator.mozLoop); var notifications = new sharedModels.NotificationCollection(); var dispatcher = new loop.Dispatcher(); var roomStore = new loop.store.RoomStore(dispatcher, { mozLoop: navigator.mozLoop, notifications: notifications }); React.render(, document.querySelector("#main")); document.documentElement.setAttribute("lang", mozL10n.getLanguage()); document.documentElement.setAttribute("dir", mozL10n.getDirection()); document.body.setAttribute("platform", loop.shared.utils.getPlatform()); // Notify the window that we've finished initalization and initial layout var evtObject = document.createEvent("Event"); evtObject.initEvent("loopPanelInitialized", true, false); window.dispatchEvent(evtObject); } return { AccountLink: AccountLink, AvailabilityDropdown: AvailabilityDropdown, ConversationDropdown: ConversationDropdown, GettingStartedView: GettingStartedView, init: init, NewRoomView: NewRoomView, PanelView: PanelView, RoomEntry: RoomEntry, RoomEntryContextButtons: RoomEntryContextButtons, RoomList: RoomList, SettingsDropdown: SettingsDropdown, SignInRequestView: SignInRequestView, ToSView: ToSView }; })(_, document.mozL10n); document.addEventListener("DOMContentLoaded", loop.panel.init);