Skip to content
Snippets Groups Projects
home-root.js 10.61 KiB
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 { playVideoWithStopOnBlur } from "../utils/video-utils.js";
import homeVideoWebM from "../assets/video/home.webm";
import homeVideoMp4 from "../assets/video/home.mp4";
import hubLogo from "../assets/images/hub-preview-light-no-shadow.png";
import mozLogo from "../assets/images/moz-logo-black.png";
import classNames from "classnames";
import { ENVIRONMENT_URLS } from "../assets/environments/environments";
import { connectToReticulum } from "../utils/phoenix-utils";

import styles from "../assets/stylesheets/index.scss";

import HubCreatePanel from "./hub-create-panel.js";
import AuthDialog from "./auth-dialog.js";
import ReportDialog from "./report-dialog.js";
import JoinUsDialog from "./join-us-dialog.js";
import UpdatesDialog from "./updates-dialog.js";
import DialogContainer from "./dialog-container.js";
import { WithHoverSound } from "./wrap-with-audio";

addLocaleData([...en]);

class HomeRoot extends Component {
  static propTypes = {
    intl: PropTypes.object,
    sceneId: PropTypes.string,
    authVerify: PropTypes.bool,
    authTopic: PropTypes.string,
    authToken: PropTypes.string,
    authOrigin: PropTypes.string,
    listSignup: PropTypes.bool,
    report: PropTypes.bool,
    initialEnvironment: PropTypes.string
  };

  state = {
    environments: [],
    dialog: null,
    mailingListEmail: "",
    mailingListPrivacy: false
  };

  componentDidMount() {
    this.closeDialog = this.closeDialog.bind(this);
    if (this.props.authVerify) {
      this.showAuthDialog(true);
      this.verifyAuth().then(this.showAuthDialog);
      return;
    }
    if (this.props.sceneId) {
      this.loadEnvironmentFromScene();
    } else {
      this.loadEnvironments();
    }
    this.loadHomeVideo();
    if (this.props.listSignup) {
      this.showUpdatesDialog();
    } else if (this.props.report) {
      this.showReportDialog();
    }
  }

  async verifyAuth() {
    const socket = connectToReticulum();
    const channel = socket.channel(this.props.authTopic);
    await new Promise((resolve, reject) =>
      channel
        .join()
        .receive("ok", resolve)
        .receive("error", reject)
    );
    channel.push("auth_verified", { token: this.props.authToken });
  }

  showAuthDialog = verifying => {
    this.setState({ dialog: <AuthDialog verifying={verifying} authOrigin={this.props.authOrigin} /> });
  };

  loadHomeVideo = () => {
    const videoEl = document.querySelector("#background-video");
    videoEl.playbackRate = 0.9;
    playVideoWithStopOnBlur(videoEl);
  };

  closeDialog() {
    this.setState({ dialog: null });
  }

  showJoinUsDialog() {
    this.setState({ dialog: <JoinUsDialog onClose={this.closeDialog} /> });
  }

  showReportDialog() {
    this.setState({ dialog: <ReportDialog onClose={this.closeDialog} /> });
  }

  showUpdatesDialog() {
    this.setState({
      dialog: <UpdatesDialog onClose={this.closeDialog} onSubmittedEmail={() => this.showEmailSubmittedDialog()} />
    });
  }

  showEmailSubmittedDialog() {
    this.setState({
      dialog: (
        <DialogContainer onClose={this.closeDialog}>
          Great! Please check your e-mail to confirm your subscription.
        </DialogContainer>
      )
    });
  }

  loadEnvironmentFromScene = async () => {
    let sceneUrlBase = "/api/v1/scenes";
    if (process.env.RETICULUM_SERVER) {
      sceneUrlBase = `https://${process.env.RETICULUM_SERVER}${sceneUrlBase}`;
    }
    const sceneInfoUrl = `${sceneUrlBase}/${this.props.sceneId}`;
    const resp = await fetch(sceneInfoUrl).then(r => r.json());
    const scene = resp.scenes[0];
    const attribution = scene.attribution && scene.attribution.split("\n").join(", ");
    const authors = attribution && [{ organization: { name: attribution } }];
    // Transform the scene info into a an environment bundle structure.
    this.setState({
      environments: [
        {
          scene_id: this.props.sceneId,
          meta: {
            title: scene.name,
            authors,
            images: [{ type: "preview-thumbnail", srcset: scene.screenshot_url }]
          }
        }
      ]
    });
  };

  loadEnvironments = () => {
    const environments = [];

    const environmentLoads = ENVIRONMENT_URLS.map(src =>
      (async () => {
        const res = await fetch(src);
        const data = await res.json();
        data.bundle_url = src;
        environments.push(data);
      })()
    );

    Promise.all(environmentLoads).then(() => this.setState({ environments }));
  };

  onDialogLinkClicked = trigger => {
    return e => {
      e.preventDefault();
      e.stopPropagation();
      trigger();
    };
  };

  render() {
    const mainContentClassNames = classNames({
      [styles.mainContent]: true,
      [styles.noninteractive]: !!this.state.dialog
    });

    return (
      <IntlProvider locale={lang} messages={messages}>
        <div className={styles.home}>
          <div className={mainContentClassNames}>
            <div className={styles.headerContent}>
              <div className={styles.titleAndNav} onClick={() => (document.location = "/")}>
                <div className={styles.links}>
                  <WithHoverSound>
                    <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener">
                      <FormattedMessage id="home.source_link" />
                    </a>
                  </WithHoverSound>
                  <WithHoverSound>
                    <a href="https://discord.gg/XzrGUY8" rel="noreferrer noopener">
                      <FormattedMessage id="home.community_link" />
                    </a>
                  </WithHoverSound>
                  <WithHoverSound>
                    <a href="/spoke" rel="noreferrer noopener">
                      Spoke
                    </a>
                  </WithHoverSound>
                </div>
              </div>
            </div>
            <div className={styles.heroContent}>
              <div className={styles.attribution}>
                Medieval Fantasy Book by{" "}
                <a
                  target="_blank"
                  rel="noreferrer noopener"
                  href="https://sketchfab.com/models/06d5a80a04fc4c5ab552759e9a97d91a?utm_campaign=06d5a80a04fc4c5ab552759e9a97d91a&utm_medium=embed&utm_source=oembed"
                >
                  Pixel
                </a>
              </div>
              <div className={styles.container}>
                <img className={styles.logo} src={hubLogo} />
                <div className={styles.title}>
                  <FormattedMessage id="home.hero_title" />
                </div>
                {this.state.environments.length === 0 && (
                  <div className="loader-wrap">
                    <div className="loader">
                      <div className="loader-center" />
                    </div>
                  </div>
                )}
              </div>
              <div className={styles.create}>
                <HubCreatePanel
                  initialEnvironment={this.props.initialEnvironment}
                  environments={this.state.environments}
                />
              </div>
              {this.state.environments.length > 1 && (
                <div>
                  <WithHoverSound>
                    <div className={styles.joinButton}>
                      <a href="/link">
                        <FormattedMessage id="home.join_room" />
                      </a>
                    </div>
                  </WithHoverSound>
                  <WithHoverSound>
                    <div className={styles.spokeButton}>
                      <a href="/spoke">
                        <FormattedMessage id="home.create_with_spoke" />
                      </a>
                    </div>
                  </WithHoverSound>
                </div>
              )}
            </div>
            <div className={styles.footerContent}>
              <div className={styles.links}>
                <div className={styles.top}>
                  <WithHoverSound>
                    <a
                      className={styles.link}
                      rel="noopener noreferrer"
                      href="#"
                      onClick={this.onDialogLinkClicked(this.showJoinUsDialog.bind(this))}
                    >
                      <FormattedMessage id="home.join_us" />
                    </a>
                  </WithHoverSound>
                  <WithHoverSound>
                    <a
                      className={styles.link}
                      rel="noopener noreferrer"
                      href="#"
                      onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))}
                    >
                      <FormattedMessage id="home.get_updates" />
                    </a>
                  </WithHoverSound>
                  <WithHoverSound>
                    <a
                      className={styles.link}
                      rel="noopener noreferrer"
                      href="#"
                      onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))}
                    >
                      <FormattedMessage id="home.report_issue" />
                    </a>
                  </WithHoverSound>
                  <WithHoverSound>
                    <a
                      className={styles.link}
                      target="_blank"
                      rel="noopener noreferrer"
                      href="https://github.com/mozilla/hubs/blob/master/TERMS.md"
                    >
                      <FormattedMessage id="home.terms_of_use" />
                    </a>
                  </WithHoverSound>
                  <WithHoverSound>
                    <a
                      className={styles.link}
                      target="_blank"
                      rel="noopener noreferrer"
                      href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"
                    >
                      <FormattedMessage id="home.privacy_notice" />
                    </a>
                  </WithHoverSound>

                  <img className={styles.mozLogo} src={mozLogo} />
                </div>
              </div>
            </div>
          </div>
          <video playsInline muted loop autoPlay className={styles.backgroundVideo} id="background-video">
            <source src={homeVideoWebM} type="video/webm" />
            <source src={homeVideoMp4} type="video/mp4" />
          </video>
          {this.state.dialog}
        </div>
      </IntlProvider>
    );
  }
}

export default HomeRoot;