import React, { Component } from "react"; import PropTypes from "prop-types"; import { injectIntl, FormattedMessage } from "react-intl"; import { generateHubName } from "../utils/name-generation"; import { faAngleLeft } from "@fortawesome/free-solid-svg-icons/faAngleLeft"; import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { resolveURL, extractUrlBase } from "../utils/resolveURL"; import { getReticulumFetchUrl } from "../utils/phoenix-utils"; import CreateRoomDialog from "./create-room-dialog.js"; import { WithHoverSound } from "./wrap-with-audio"; import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; import styles from "../assets/stylesheets/hub-create.scss"; const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$"; class HubCreatePanel extends Component { static propTypes = { intl: PropTypes.object, environments: PropTypes.array, initialEnvironment: PropTypes.string }; constructor(props) { super(props); let environmentIndex = Math.floor(Math.random() * props.environments.length); if (props.initialEnvironment) { environmentIndex = props.environments.findIndex( e => e.name.toLowerCase() === props.initialEnvironment.toLowerCase() ); } this.state = { ready: false, name: generateHubName(), environmentIndex, showCustomSceneDialog: false, customSceneUrl: null }; // Optimisticly preload all environment thumbnails (async () => { const environmentThumbnails = props.environments.map((_, i) => this._getEnvironmentThumbnail(i)); await Promise.all( environmentThumbnails.map(environmentThumbnail => this._preloadImage(environmentThumbnail.srcset)) ); this.setState({ ready: true }); })(); } _getEnvironmentThumbnail = environmentIndex => { const environment = this.props.environments[environmentIndex]; const meta = environment.meta || {}; let environmentThumbnail = { srcset: default_scene_preview_thumbnail }; if (meta.images) { const thumbnailImage = meta.images.find(i => i.type === "preview-thumbnail"); if (thumbnailImage) { // TODO kill bundles if (environment.bundle_url) { const baseURL = new URL(extractUrlBase(environment.bundle_url), window.location.href); environmentThumbnail = { srcset: resolveURL(thumbnailImage.srcset, baseURL) }; } else { environmentThumbnail = { srcset: thumbnailImage.srcset }; } } } return environmentThumbnail; }; createHub = async e => { if (e) { e.preventDefault(); } const environment = this.props.environments[this.state.environmentIndex]; const payload = { hub: { name: this.state.name } }; if (!this.state.customSceneUrl && environment.scene_id) { payload.hub.scene_id = environment.scene_id; } else { const sceneUrl = this.state.customSceneUrl || environment.bundle_url; payload.hub.default_environment_gltf_bundle_url = sceneUrl; } const createUrl = getReticulumFetchUrl("/api/v1/hubs"); const res = await fetch(createUrl, { body: JSON.stringify(payload), headers: { "content-type": "application/json" }, method: "POST" }); const hub = await res.json(); if (!process.env.RETICULUM_SERVER || document.location.host === process.env.RETICULUM_SERVER) { document.location = hub.url; } else { document.location = `/hub.html?hub_id=${hub.hub_id}`; } }; isHubNameValid = () => { const hubAlphaPattern = "[A-Za-z0-9]{4}"; return new RegExp(HUB_NAME_PATTERN).test(this.state.name) && new RegExp(hubAlphaPattern).test(this.state.name); }; _preloadImage = async srcset => { const img = new Image(); const imgLoad = new Promise(resolve => img.addEventListener("load", resolve)); img.srcset = srcset; await imgLoad; }; setToEnvironmentOffset = async offset => { const numEnvs = this.props.environments.length; const environmentIndex = (((this.state.environmentIndex + offset) % numEnvs) + numEnvs) % numEnvs; const environmentThumbnail = this._getEnvironmentThumbnail(environmentIndex); await this._preloadImage(environmentThumbnail.srcset); this.setState({ environmentIndex }); }; setToNextEnvironment = () => { this.setToEnvironmentOffset(1); }; setToPreviousEnvironment = () => { this.setToEnvironmentOffset(-1); }; showCustomSceneDialog = e => { e.preventDefault(); e.stopPropagation(); this.setState({ showCustomSceneDialog: true }); }; shuffle = () => { this.setState({ name: generateHubName(), environmentIndex: Math.floor(Math.random() * this.props.environments.length) }); }; render() { if (!this.state.ready) return null; if (this.props.environments.length == 0) { return <div className={styles.placeholder} />; } const environment = this.props.environments[this.state.environmentIndex]; const meta = environment.meta || {}; const environmentTitle = meta.title || environment.name; const environmentAuthor = (meta.authors || [])[0]; const environmentThumbnail = this._getEnvironmentThumbnail(this.state.environmentIndex); return ( <div> <form onSubmit={this.createHub}> <div className={styles.createPanel}> <div className={styles.form}> <div className={styles.environment}> <div className={styles.picker}> <img className={styles.image} srcSet={environmentThumbnail.srcset} /> <div className={styles.labels}> <div className={styles.header}> <span className={styles.title}>{environmentTitle}</span> {environmentAuthor && environmentAuthor.name && (environmentAuthor.url ? ( <WithHoverSound> <a href={environmentAuthor.url} rel="noopener noreferrer" className={styles.author}> <FormattedMessage id="home.environment_author_by" /> <span>{environmentAuthor.name}</span> </a> </WithHoverSound> ) : ( <span className={styles.author}> <FormattedMessage id="home.environment_author_by" /> <span>{environmentAuthor.name}</span> </span> ))} {environmentAuthor && environmentAuthor.organization && (environmentAuthor.organization.url ? ( <WithHoverSound> <a href={environmentAuthor.organization.url} rel="noopener noreferrer" className={styles.org} > <span>{environmentAuthor.organization.name}</span> </a> </WithHoverSound> ) : ( <span className={styles.org}> <span>{environmentAuthor.organization.name}</span> </span> ))} </div> <div className={styles.footer}> <WithHoverSound> <button onClick={this.showCustomSceneDialog} className={styles.customButton}> <FormattedMessage id="home.room_create_options" /> </button> </WithHoverSound> </div> </div> <div className={styles.controls}> <WithHoverSound> <button className={styles.prev} type="button" tabIndex="1" onClick={this.setToPreviousEnvironment} > <FontAwesomeIcon icon={faAngleLeft} /> </button> </WithHoverSound> <WithHoverSound> <button className={styles.next} type="button" tabIndex="2" onClick={this.setToNextEnvironment}> <FontAwesomeIcon icon={faAngleRight} /> </button> </WithHoverSound> </div> </div> </div> <div className={styles.container}> <WithHoverSound> <button type="submit" tabIndex="5" className={styles.submitButton}> <FormattedMessage id="home.room_create_button" /> </button> </WithHoverSound> </div> </div> </div> </form> {this.state.showCustomSceneDialog && ( <CreateRoomDialog includeScenePrompt={true} onClose={() => this.setState({ showCustomSceneDialog: false })} onCustomScene={(name, url) => { this.setState({ showCustomSceneDialog: false, name: name, customSceneUrl: url }, () => this.createHub()); }} /> )} </div> ); } } export default injectIntl(HubCreatePanel);