-
Greg Fodor authoredGreg Fodor authored
hub-create-panel.js 10.29 KiB
import React, { Component } from "react";
import PropTypes from "prop-types";
import { injectIntl, FormattedMessage } from "react-intl";
import { generateHubName } from "../utils/name-generation";
import classNames from "classnames";
import faAngleLeft from "@fortawesome/fontawesome-free-solid/faAngleLeft";
import faAngleRight from "@fortawesome/fontawesome-free-solid/faAngleRight";
import FontAwesomeIcon from "@fortawesome/react-fontawesome";
import { resolveURL, extractUrlBase } from "../utils/resolveURL";
import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png";
const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$";
class HubCreatePanel extends Component {
static propTypes = {
intl: PropTypes.object,
environments: PropTypes.array
};
constructor(props) {
super(props);
this.state = {
ready: false,
name: generateHubName(),
environmentIndex: Math.floor(Math.random() * props.environments.length),
// HACK: expand on small screens by default to ensure scene selection possible.
// Eventually this could/should be done via media queries.
expanded: window.innerWidth < 420
};
// 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) {
const baseURL = new URL(extractUrlBase(environment.bundle_url), window.location.href);
environmentThumbnail = {
srcset: resolveURL(thumbnailImage.srcset, baseURL)
};
}
}
return environmentThumbnail;
};
createHub = async e => {
e.preventDefault();
const environment = this.props.environments[this.state.environmentIndex];
const payload = {
hub: { name: this.state.name, default_environment_gltf_bundle_url: environment.bundle_url }
};
let createUrl = "/api/v1/hubs";
if (process.env.NODE_ENV === "development") {
createUrl = `https://${process.env.DEV_RETICULUM_SERVER}${createUrl}`;
}
const res = await fetch(createUrl, {
body: JSON.stringify(payload),
headers: { "content-type": "application/json" },
method: "POST"
});
const hub = await res.json();
if (process.env.NODE_ENV === "production") {
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);
};
shuffle = () => {
this.setState({
name: generateHubName(),
environmentIndex: Math.floor(Math.random() * this.props.environments.length)
});
};
render() {
if (!this.state.ready) return null;
const { formatMessage } = this.props.intl;
if (this.props.environments.length == 0) {
return <div />;
}
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);
const formNameClassNames = classNames("create-panel__form__name", {
"create-panel__form__name--expanded": this.state.expanded
});
return (
<form onSubmit={this.createHub}>
<div className="create-panel">
{!this.state.expanded && (
<div className="create-panel__header">
<FormattedMessage id="home.create_header" />
</div>
)}
<div className="create-panel__form">
<div
className="create-panel__form__left-container"
onClick={async () => {
if (this.state.expanded) {
this.shuffle();
} else {
await this._preloadImage(this._getEnvironmentThumbnail(this.state.environmentIndex).srcset);
this.setState({ expanded: true });
}
}}
>
<button type="button" tabIndex="3" className="create-panel__form__rotate-button">
{this.state.expanded ? (
<img src="../assets/images/dice_icon.svg" />
) : (
<img src="../assets/images/expand_dots_icon.svg" />
)}
</button>
</div>
<div className="create-panel__form__right-container">
<button type="submit" tabIndex="5" className="create-panel__form__submit-button">
{this.isHubNameValid() ? (
<img src="../assets/images/hub_create_button_enabled.svg" />
) : (
<img src="../assets/images/hub_create_button_disabled.svg" />
)}
</button>
</div>
{this.state.expanded && (
<div className="create-panel__form__environment">
<div className="create-panel__form__environment__picker">
<img
className="create-panel__form__environment__picker__image"
srcSet={environmentThumbnail.srcset}
/>
<div className="create-panel__form__environment__picker__labels">
<div className="create-panel__form__environment__picker__labels__header">
<span className="create-panel__form__environment__picker__labels__header__title">
{environmentTitle}
</span>
{environmentAuthor &&
environmentAuthor.name &&
(environmentAuthor.url ? (
<a
href={environmentAuthor.url}
target="_blank"
rel="noopener noreferrer"
className="create-panel__form__environment__picker__labels__header__author"
>
<FormattedMessage id="home.environment_author_by" />
<span>{environmentAuthor.name}</span>
</a>
) : (
<span className="create-panel__form__environment__picker__labels__header__author">
<FormattedMessage id="home.environment_author_by" />
<span>{environmentAuthor.name}</span>
</span>
))}
{environmentAuthor &&
environmentAuthor.organization &&
(environmentAuthor.organization.url ? (
<a
href={environmentAuthor.organization.url}
target="_blank"
rel="noopener noreferrer"
className="create-panel__form__environment__picker__labels__header__org"
>
<span>{environmentAuthor.organization.name}</span>
</a>
) : (
<span className="create-panel__form__environment__picker__labels__header__org">
<span>{environmentAuthor.organization.name}</span>
</span>
))}
</div>
<div className="create-panel__form__environment__picker__labels__footer">
<FormattedMessage id="home.environment_picker_footer" />
</div>
</div>
<div className="create-panel__form__environment__picker__controls">
<button
className="create-panel__form__environment__picker__controls__prev"
type="button"
tabIndex="1"
onClick={this.setToPreviousEnvironment}
>
<FontAwesomeIcon icon={faAngleLeft} />
</button>
<button
className="create-panel__form__environment__picker__controls__next"
type="button"
tabIndex="2"
onClick={this.setToNextEnvironment}
>
<FontAwesomeIcon icon={faAngleRight} />
</button>
</div>
</div>
</div>
)}
<input
tabIndex="4"
className={formNameClassNames}
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
onFocus={e => e.target.select()}
required
pattern={HUB_NAME_PATTERN}
title={formatMessage({ id: "home.create_name.validation_warning" })}
/>
</div>
</div>
</form>
);
}
}
export default injectIntl(HubCreatePanel);