From 7d564db0c134b5e46bf822cac0352316d84fd4df Mon Sep 17 00:00:00 2001 From: Greg Fodor <gfodor@gmail.com> Date: Tue, 8 May 2018 14:45:06 -0700 Subject: [PATCH] Link to device dialog working --- src/assets/stylesheets/link-dialog.scss | 27 ++++++++++++++ src/assets/translations.data.json | 6 ++- src/hub.js | 2 +- src/react-components/info-dialog.js | 7 ++-- src/react-components/link-dialog.js | 49 +++++++++++++++++++++---- src/react-components/ui-root.js | 23 +++++++++++- src/utils/link-channel.js | 17 +++++++-- 7 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 src/assets/stylesheets/link-dialog.scss diff --git a/src/assets/stylesheets/link-dialog.scss b/src/assets/stylesheets/link-dialog.scss new file mode 100644 index 000000000..f9b18bf26 --- /dev/null +++ b/src/assets/stylesheets/link-dialog.scss @@ -0,0 +1,27 @@ +:local(.domain) { + color: white; + font-size: 3.0em; + font-family: monospace; + font-weight: bold; + padding: 14px; +} + +:local(.code) { + color: white; + font-size: 4.0em; + font-family: monospace; + font-weight: bold; + padding: 8px; +} + +:local(.keep-open) { + font-size: 0.8em; +} + +:local(.digit) { + padding: 0px 8px; +} + +:local(.code-loading-panel) { + background: transparent; +} diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 27c3ffefa..02a9a318a 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -60,6 +60,10 @@ "home.environment_author_by": " by ", "home.dialog.close": "CLOSE", "mailing_list.privacy_label": "I'm okay with Mozilla handling my info as explained in", - "mailing_list.privacy_link": "this Privacy Notice" + "mailing_list.privacy_link": "this Privacy Notice", + "link.in_your_browser": "In your device's browser, go to:", + "link.link_domain": "hub.link", + "link.enter_code": "Then, enter code:", + "link.do_not_close": "Keep this dialog open to use this code." } } diff --git a/src/hub.js b/src/hub.js index 2d3ea5d9c..6f36492b1 100644 --- a/src/hub.js +++ b/src/hub.js @@ -172,7 +172,7 @@ const onReady = async () => { registerNetworkSchemas(); - let uiProps = {}; + let uiProps = { linkChannel }; mountUI(scene); diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js index a502fe83a..41eab23e9 100644 --- a/src/react-components/info-dialog.js +++ b/src/react-components/info-dialog.js @@ -22,8 +22,7 @@ class InfoDialog extends Component { dialogType: PropTypes.oneOf(Object.values(InfoDialog.dialogTypes)), onCloseDialog: PropTypes.func, onSubmittedEmail: PropTypes.func, - linkChannel: PropTypes.object, - onLinkCodeUsed: PropTypes.func + linkCode: PropTypes.string }; constructor(props) { @@ -219,8 +218,8 @@ class InfoDialog extends Component { ); break; case InfoDialog.dialogTypes.link: - dialogTitle = "Enter on Device"; - dialogBody = <LinkDialog linkChannel={this.props.linkChannel} onLinkCodeUsed={this.props.onLinkCodeUseds} />; + dialogTitle = "Link to Device"; + dialogBody = <LinkDialog linkCode={this.props.linkCode} />; break; } diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js index e12aa237a..c6d8305f8 100644 --- a/src/react-components/link-dialog.js +++ b/src/react-components/link-dialog.js @@ -1,20 +1,53 @@ import React, { Component } from "react"; -import classNames from "classnames"; import PropTypes from "prop-types"; +import classNames from "classnames"; import { FormattedMessage } from "react-intl"; -class LinkDialog extends Component { - state = { - code: null - }; +import styles from "../assets/stylesheets/link-dialog.scss"; +class LinkDialog extends Component { static propTypes = { - linkChannel: PropTypes.object, - onLinkCodeUsed: PropTypes.func + linkCode: PropTypes.string }; render() { - return <div>Hello</div>; + if (!this.props.linkCode) { + return ( + <div> + <div className={classNames("loading-panel", styles.codeLoadingPanel)}> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> + </div> + </div> + ); + } + + return ( + <div> + <div> + <FormattedMessage id="link.in_your_browser" /> + </div> + <div className={styles.domain}> + <FormattedMessage id="link.link_domain" /> + </div> + <div> + <FormattedMessage id="link.enter_code" /> + </div> + <div className={styles.code}> + {this.props.linkCode.split("").map((d, i) => ( + <span className={styles.digit} key={i}> + {d} + </span> + ))} + </div> + <div className={styles.keepOpen}> + <FormattedMessage id="link.do_not_close" /> + </div> + </div> + ); } } diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 8ec0f4a99..b390773ed 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -66,6 +66,7 @@ class UIRoot extends Component { enableScreenSharing: PropTypes.bool, store: PropTypes.object, scene: PropTypes.object, + linkChannel: PropTypes.object, htmlPrefix: PropTypes.string, showProfileEntry: PropTypes.bool, availableVREntryTypes: PropTypes.object, @@ -81,6 +82,8 @@ class UIRoot extends Component { entryStep: ENTRY_STEPS.start, enterInVR: false, infoDialogType: null, + linkCode: null, + linkCodeCancel: null, shareScreen: false, requestedScreen: false, @@ -519,6 +522,21 @@ class UIRoot extends Component { this.setState({ entryStep: ENTRY_STEPS.finished }); }; + attemptLink = async () => { + this.setState({ infoDialogType: InfoDialog.dialogTypes.link }); + const { code, cancel, onFinished } = await this.props.linkChannel.generateCode(); + this.setState({ linkCode: code, linkCodeCancel: cancel }); + onFinished.then(this.handleCloseDialog); + }; + + handleCloseDialog = async () => { + if (this.state.linkCodeCancel) { + this.state.linkCodeCancel(); + } + + this.setState({ infoDialogType: null, linkCode: null, linkCodeCancel: null }); + }; + render() { if (this.state.exited || this.props.roomUnavailableReason || this.props.platformUnsupportedReason) { let subtitle = null; @@ -618,7 +636,7 @@ class UIRoot extends Component { {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( <GenericEntryButton onClick={this.enterVR} /> )} - <DeviceEntryButton onClick={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.link })} /> + <DeviceEntryButton onClick={this.attemptLink} /> {this.props.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && ( <GearVREntryButton onClick={this.enterGearVR} /> )} @@ -836,8 +854,9 @@ class UIRoot extends Component { <div className="ui"> <InfoDialog dialogType={this.state.infoDialogType} + linkCode={this.state.linkCode} onSubmittedEmail={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.email_submitted })} - onCloseDialog={() => this.setState({ infoDialogType: null })} + onCloseDialog={this.handleCloseDialog} /> {this.state.entryStep === ENTRY_STEPS.finished && ( diff --git a/src/utils/link-channel.js b/src/utils/link-channel.js index 418b6d738..4f61fd3aa 100644 --- a/src/utils/link-channel.js +++ b/src/utils/link-channel.js @@ -28,9 +28,16 @@ export default class LinkChannel { // Only respond to one link_request in this channel. let readyToSend = false; + let leftChannel = false; const channel = this.socket.channel(`link:${code}`, { timeout: 10000 }); - const cancel = () => channel.leave(); + + const leave = () => { + if (!leftChannel) channel.leave(); + leftChannel = true; + }; + + const cancel = () => leave(); channel.on("link_expired", () => finished("expired")); @@ -63,8 +70,12 @@ export default class LinkChannel { data: encryptedData }; - channel.push("link_response", payload); - channel.leave(); + if (!leftChannel) { + channel.push("link_response", payload); + } + + leave(); + finished("used"); readyToSend = false; } -- GitLab