Skip to content
Snippets Groups Projects
link-root.js 9.38 KiB
Newer Older
Greg Fodor's avatar
Greg Fodor committed
import React, { Component } from "react";
import PropTypes from "prop-types";
import { IntlProvider, FormattedMessage, addLocaleData } from "react-intl";
import en from "react-intl/locale-data/en";

import { lang, messages } from "../utils/i18n";
import classNames from "classnames";
import styles from "../assets/stylesheets/link.scss";
import { disableiOSZoom } from "../utils/disable-ios-zoom";
Greg Fodor's avatar
Greg Fodor committed
import HeadsetIcon from "../assets/images/generic_vr_entry.svg";
import { WithHoverSound } from "./wrap-with-audio";
Greg Fodor's avatar
Greg Fodor committed

Greg Fodor's avatar
Greg Fodor committed
const MAX_DIGITS = 6;
Greg Fodor's avatar
Greg Fodor committed
const MAX_LETTERS = 4;
Greg Fodor's avatar
Greg Fodor committed
addLocaleData([...en]);
Greg Fodor's avatar
Greg Fodor committed
const hasTouchEvents = "ontouchstart" in document.documentElement;
Greg Fodor's avatar
Greg Fodor committed

class LinkRoot extends Component {
  static propTypes = {
Greg Fodor's avatar
Greg Fodor committed
    intl: PropTypes.object,
    store: PropTypes.object,
    linkChannel: PropTypes.object,
    showHeadsetLinkOption: PropTypes.bool
Greg Fodor's avatar
Greg Fodor committed
    entered: "",
    isAlphaMode: false,
    failedAtLeastOnce: false
  };

Marshall Quander's avatar
Marshall Quander committed
  componentDidMount = () => {
Greg Fodor's avatar
Greg Fodor committed
    document.addEventListener("keydown", this.handleKeyDown);
  };

  componentWillUnmount = () => {
    document.removeEventListener("keydown", this.handleKeyDown);
  };

  handleKeyDown = e => {
Greg Fodor's avatar
Greg Fodor committed
    // Number keys 0-9
Greg Fodor's avatar
Greg Fodor committed
    if ((e.keyCode < 48 || e.keyCode > 57) && !this.state.isAlphaMode) {
      return;
    }

    // Alpha keys A-I
    if ((e.keyCode < 65 || e.keyCode > 73) && this.state.isAlphaMode) {
Greg Fodor's avatar
Greg Fodor committed
      return;
    }

    e.preventDefault();
    e.stopPropagation();

Greg Fodor's avatar
Greg Fodor committed
    if (this.state.isAlphaMode) {
      this.addToEntry("IHGFEDCBA"[73 - e.keyCode]);
    } else {
      this.addToEntry(e.keyCode - 48);
    }
  };

  maxAllowedChars = () => {
    return this.state.isAlphaMode ? MAX_LETTERS : MAX_DIGITS;
Greg Fodor's avatar
Greg Fodor committed
  };

Greg Fodor's avatar
Greg Fodor committed
  addToEntry = ch => {
    if (this.state.entered.length >= this.maxAllowedChars()) return;
    const newChars = `${this.state.entered}${ch}`;
Greg Fodor's avatar
Greg Fodor committed
    if (newChars.length === this.maxAllowedChars()) {
      this.attemptLookup(newChars);
Greg Fodor's avatar
Greg Fodor committed
    this.setState({ entered: newChars });
Greg Fodor's avatar
Greg Fodor committed
  removeChar = () => {
    const entered = this.state.entered;
    if (entered.length === 0) return;
    this.setState({ entered: entered.substring(0, entered.length - 1) });
Greg Fodor's avatar
Greg Fodor committed
  };

Greg Fodor's avatar
Greg Fodor committed
  attemptLink = async code => {
Greg Fodor's avatar
Greg Fodor committed
    this.props.linkChannel
      .attemptLink(code)
      .then(response => {
        // If there is a profile from the linked device, copy it over if we don't have one yet.
        if (response.profile) {
          const { hasChangedName } = this.props.store.state.activity;

          if (!hasChangedName) {
            this.props.store.update({ activity: { hasChangedName: true }, profile: response.profile });
          }
        }

        if (response.path) {
          window.location.href = response.path;
        }
      })
      .catch(e => {
Greg Fodor's avatar
Greg Fodor committed
        this.setState({ failedAtLeastOnce: true, entered: "" });
Greg Fodor's avatar
Greg Fodor committed

        if (!(e instanceof Error && (e.message === "in_use" || e.message === "failed"))) {
          throw e;
        }
      });
  };

  attemptEntry = async code => {
    const url = "/link/" + code;
Greg Fodor's avatar
Greg Fodor committed
    const res = await fetch(url);

    if (res.status >= 400) {
Greg Fodor's avatar
Greg Fodor committed
      this.setState({ failedAtLeastOnce: true, entered: "" });
Greg Fodor's avatar
Greg Fodor committed
    } else {
      document.location = url;
    }
Greg Fodor's avatar
Greg Fodor committed

Greg Fodor's avatar
Greg Fodor committed
  attemptLookup = async code => {
    if (this.state.isAlphaMode) {
      // Headset link code
      this.attemptLink(code);
    } else {
      // Room entry code
      this.attemptEntry(code);
    }
  };

  toggleMode = () => {
Greg Fodor's avatar
Greg Fodor committed
    this.setState({ isAlphaMode: !this.state.isAlphaMode, entered: "", failedAtLeastOnce: false });
Greg Fodor's avatar
Greg Fodor committed
  render() {
Greg Fodor's avatar
Greg Fodor committed
    // Note we use type "tel" for the input due to https://bugzilla.mozilla.org/show_bug.cgi?id=1005603

Greg Fodor's avatar
Greg Fodor committed
    return (
      <IntlProvider locale={lang} messages={messages}>
        <div className={styles.link}>
          <div className={styles.linkContents}>
            <div className={styles.logo}>
              <img src="../assets/images/hub-preview-light-no-shadow.png" />
            </div>
            {this.state.entered.length === this.maxAllowedChars() && (
              <div className={classNames("loading-panel", styles.codeLoadingPanel)}>
                <div className="loader-wrap">
                  <div className="loader">
                    <div className="loader-center" />
johnshaughnessy's avatar
johnshaughnessy committed
                  </div>
                </div>
              </div>
            )}

            <div className={styles.enteredContents}>
              <div className={styles.header}>
                <FormattedMessage
                  id={
                    this.state.failedAtLeastOnce
                      ? "link.try_again"
                      : "link.link_page_header_" + (!this.state.isAlphaMode ? "entry" : "headset")
                  }
                />
              </div>
              <div className={styles.entered}>
                <input
                  className={styles.charInput}
                  type={this.state.isAlphaMode ? "text" : "tel"}
                  pattern="[0-9A-I]*"
                  value={this.state.entered}
                  onChange={ev => {
                    if (!this.state.isAlphaMode && ev.target.value.match(/[a-z]/i)) {
                      this.setState({ isAlphaMode: true });
                    }

                    this.setState({ entered: ev.target.value.toUpperCase() });
                  }}
                />
              </div>
              <div className={styles.enteredFooter}>
                {!this.state.isAlphaMode &&
                  this.props.showHeadsetLinkOption && (
                    <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
                  )}
                {!this.state.isAlphaMode &&
                  this.props.showHeadsetLinkOption && (
                    <span>
                      <WithHoverSound>
                        <a href="#" onClick={() => this.toggleMode()}>
                          <FormattedMessage id="link.linking_a_headset" />
                        </a>
                      </WithHoverSound>
            <div className={styles.keypad}>
              {(this.state.isAlphaMode
                ? ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
                : [1, 2, 3, 4, 5, 6, 7, 8, 9]
              ).map((d, i) => (
johnshaughnessy's avatar
johnshaughnessy committed
                  <button
                    disabled={this.state.entered.length === this.maxAllowedChars()}
                    className={styles.keypadButton}
johnshaughnessy's avatar
johnshaughnessy committed
                    onClick={() => {
                      if (!hasTouchEvents) this.addToEntry(d);
                    onTouchStart={() => this.addToEntry(d)}
johnshaughnessy's avatar
johnshaughnessy committed
                  </button>
              {this.props.showHeadsetLinkOption ? (
                <WithHoverSound>
                  <button
                    className={classNames(styles.keypadButton, styles.keypadToggleMode)}
                    onTouchStart={() => this.toggleMode()}
                    onClick={() => {
                      if (!hasTouchEvents) this.toggleMode();
                    }}
                  >
                    {this.state.isAlphaMode ? "123" : "ABC"}
                  </button>
                </WithHoverSound>
              {!this.state.isAlphaMode && (
                <WithHoverSound>
johnshaughnessy's avatar
johnshaughnessy committed
                  <button
                    disabled={this.state.entered.length === this.maxAllowedChars()}
                    className={classNames(styles.keypadButton, styles.keypadZeroButton)}
                    onTouchStart={() => this.addToEntry(0)}
johnshaughnessy's avatar
johnshaughnessy committed
                    onClick={() => {
                      if (!hasTouchEvents) this.addToEntry(0);
johnshaughnessy's avatar
johnshaughnessy committed
                  </button>
                </WithHoverSound>
              )}
              <WithHoverSound>
                <button
                  disabled={this.state.entered.length === 0 || this.state.entered.length === this.maxAllowedChars()}
                  className={classNames(styles.keypadButton, styles.keypadBackspace)}
                  onTouchStart={() => this.removeChar()}
                  onClick={() => {
                    if (!hasTouchEvents) this.removeChar();
                  }}
                >
                  
                </button>
              </WithHoverSound>
            </div>
              {!this.state.isAlphaMode &&
                this.props.showHeadsetLinkOption && (
                  <div
                    className={styles.linkHeadsetFooterLink}
                    style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }}
                  >
                    <WithHoverSound>
                      <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} />
                    </WithHoverSound>
                      <WithHoverSound>
                        <a href="#" onClick={() => this.toggleMode()}>
                          <FormattedMessage id="link.linking_a_headset" />
                        </a>
                      </WithHoverSound>
Greg Fodor's avatar
Greg Fodor committed
            </div>
Greg Fodor's avatar
Greg Fodor committed
      </IntlProvider>
    );
  }
}

export default LinkRoot;