diff --git a/src/assets/stylesheets/hub-create.scss b/src/assets/stylesheets/hub-create.scss index 899f0bce6e444815ba455563e6c238de32db4889..cb65b6aa51483507fa9221c33fc2a91b176d5b1e 100644 --- a/src/assets/stylesheets/hub-create.scss +++ b/src/assets/stylesheets/hub-create.scss @@ -164,15 +164,24 @@ top: 0; left: 0; background: rgb(2,0,36); - background: linear-gradient(0deg, rgba(2,0,36,0) 0%, rgba(1,0,11,0.2189076314119398) 60%, rgba(0,0,0,0.5242297602634804) 100%); + background: linear-gradient(0deg, rgba(0,0,0,0.324) 0%, rgba(1,0,11,0.1189076314119398) 60%, rgba(0,0,0,0.3242297602634804) 100%); - &__footer { + &__custom_button { + @extend %default-font; + cursor: pointer; position: absolute; bottom: 14px; left: 12px; - font-size: 1.2em; + font-size: 1.3em; text-shadow: 0px 0px 6px #202020; color: $light-text; + appearance: none; + text-decoration: underline; + -moz-appearance: none; + -webkit-appearance: none; + background: transparent; + border: none; + pointer-events: auto; } &__header { diff --git a/src/assets/stylesheets/info-dialog.scss b/src/assets/stylesheets/info-dialog.scss index 58b2259fc71f0097d8f2b032b1fcc9bfa5305251..05aec48c469e721d3c90406ee62606ba9535bd92 100644 --- a/src/assets/stylesheets/info-dialog.scss +++ b/src/assets/stylesheets/info-dialog.scss @@ -79,7 +79,7 @@ } } -.invite-form, .add-media-form { +.invite-form, .add-media-form, .custom-scene-form { display: flex; flex-direction: column; align-items: center; diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 1f8ae6a1b0986b0576ad3dd567b1fc8bb96c1d86..d2587730b7c7e64261e9d6ec224c7fd23483f970 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -46,7 +46,7 @@ "autoexit.subtitle": "You have started another session.", "autoexit.cancel": "CANCEL", "home.create_header": "Name your room:", - "home.environment_picker_footer": "Choose a scene", + "home.environment_picker_footer_choose_link": "Use a custom scene", "home.create_name.validation_warning": "Invalid name, limited to 4 to 64 characters and limited symbols.", "home.webvr_disclaimer_pre": "A ", "home.webvr_disclaimer_post": " experiment by ", diff --git a/src/hub.js b/src/hub.js index 39cb6d97e42b26809be549c2d7262a9ebd489514..006805eb7d1d600fad3e29b26b5fe82fd7427e80 100644 --- a/src/hub.js +++ b/src/hub.js @@ -506,9 +506,21 @@ const onReady = async () => { .receive("ok", data => { const hub = data.hubs[0]; const defaultSpaceTopic = hub.topics[0]; - const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; + const sceneUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; + + console.log(`Scene URL: ${sceneUrl}`); + + if (/\.gltf/i.test(sceneUrl) || /\.glb/i.test(sceneUrl)) { + const gltfEl = document.createElement("a-entity"); + gltfEl.setAttribute("gltf-model-plus", { src: sceneUrl, inflate: true }); + gltfEl.addEventListener("model-loaded", () => initialEnvironmentEl.emit("bundleloaded")); + initialEnvironmentEl.appendChild(gltfEl); + } else { + // TODO remove, and remove bundleloaded event + initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${sceneUrl}`); + } + setRoom(hub.hub_id, hub.name); - initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); hubChannel.setPhoenixChannel(channel); }) .receive("error", res => { diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js index 959a4a9331a1975a83a0ab315b7fa092c8f05418..d3a83b20b4698ea99c35941015a6b9ba9f39cbe8 100644 --- a/src/react-components/hub-create-panel.js +++ b/src/react-components/hub-create-panel.js @@ -7,10 +7,12 @@ 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 InfoDialog from "./info-dialog.js"; import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$"; +const dialogTypes = InfoDialog.dialogTypes; class HubCreatePanel extends Component { static propTypes = { @@ -34,6 +36,8 @@ class HubCreatePanel extends Component { ready: false, name: generateHubName(), environmentIndex, + showCustomSceneDialog: false, + customSceneUrl: null, // 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 @@ -73,11 +77,15 @@ class HubCreatePanel extends Component { }; createHub = async e => { - e.preventDefault(); + if (e) { + e.preventDefault(); + } + const environment = this.props.environments[this.state.environmentIndex]; + const sceneUrl = this.state.customSceneUrl || environment.bundle_url; const payload = { - hub: { name: this.state.name, default_environment_gltf_bundle_url: environment.bundle_url } + hub: { name: this.state.name, default_environment_gltf_bundle_url: sceneUrl } }; let createUrl = "/api/v1/hubs"; @@ -131,6 +139,12 @@ class HubCreatePanel extends Component { this.setToEnvironmentOffset(-1); }; + showCustomSceneDialog = e => { + e.preventDefault(); + e.stopPropagation(); + this.setState({ showCustomSceneDialog: true }); + }; + shuffle = () => { this.setState({ name: generateHubName(), @@ -158,136 +172,150 @@ class HubCreatePanel extends Component { }); 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"> - {meta.url ? ( - <a - href={meta.url} - rel="noopener noreferrer" - className="create-panel__form__environment__picker__labels__header__title" - > - {environmentTitle} - </a> - ) : ( - <span className="create-panel__form__environment__picker__labels__header__title"> - environmentTitle - </span> - )} - {environmentAuthor && - environmentAuthor.name && - (environmentAuthor.url ? ( - <a - href={environmentAuthor.url} - 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 ? ( + <div> + <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"> + {meta.url ? ( <a - href={environmentAuthor.organization.url} + href={meta.url} rel="noopener noreferrer" - className="create-panel__form__environment__picker__labels__header__org" + className="create-panel__form__environment__picker__labels__header__title" > - <span>{environmentAuthor.organization.name}</span> + {environmentTitle} </a> ) : ( - <span className="create-panel__form__environment__picker__labels__header__org"> - <span>{environmentAuthor.organization.name}</span> + <span className="create-panel__form__environment__picker__labels__header__title"> + environmentTitle </span> - ))} + )} + {environmentAuthor && + environmentAuthor.name && + (environmentAuthor.url ? ( + <a + href={environmentAuthor.url} + 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} + 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> + <button + onClick={this.showCustomSceneDialog} + className="create-panel__form__environment__picker__labels__custom_button" + > + <FormattedMessage id="home.environment_picker_footer_choose_link" /> + </button> </div> - <div className="create-panel__form__environment__picker__labels__footer"> - <FormattedMessage id="home.environment_picker_footer" /> + <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 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" })} - /> + )} + <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> - </div> - </form> + </form> + {this.state.showCustomSceneDialog && ( + <InfoDialog + dialogType={dialogTypes.custom_scene} + onCloseDialog={() => this.setState({ showCustomSceneDialog: false })} + onCustomScene={url => { + this.setState({ showCustomSceneDialog: false, customSceneUrl: url }, () => this.createHub()); + }} + /> + )} + </div> ); } } diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js index 83b46b9dfee5569044581b44879566a13ec3c934..6e57b21c57beace4711ab3ef77679dffad601cd7 100644 --- a/src/react-components/info-dialog.js +++ b/src/react-components/info-dialog.js @@ -21,13 +21,15 @@ class InfoDialog extends Component { help: Symbol("help"), link: Symbol("link"), webvr_recommend: Symbol("webvr_recommend"), - add_media: Symbol("add_media") + add_media: Symbol("add_media"), + custom_scene: Symbol("custom_scene") }; static propTypes = { dialogType: PropTypes.oneOf(Object.values(InfoDialog.dialogTypes)), onCloseDialog: PropTypes.func, onSubmittedEmail: PropTypes.func, onAddMedia: PropTypes.func, + onCustomScene: PropTypes.func, linkCode: PropTypes.string }; @@ -37,8 +39,6 @@ class InfoDialog extends Component { const loc = document.location; this.shareLink = `${loc.protocol}//${loc.host}${loc.pathname}`; this.onKeyDown = this.onKeyDown.bind(this); - this.onContainerClicked = this.onContainerClicked.bind(this); - this.onAddMediaClicked = this.onAddMediaClicked.bind(this); } componentDidMount() { @@ -57,16 +57,21 @@ class InfoDialog extends Component { } } - onContainerClicked(e) { + onContainerClicked = e => { if (e.currentTarget === e.target) { this.props.onCloseDialog(); } - } + }; - onAddMediaClicked() { + onAddMediaClicked = () => { this.props.onAddMedia(this.state.addMediaUrl); this.props.onCloseDialog(); - } + }; + + onCustomSceneClicked = () => { + this.props.onCustomScene(this.state.customSceneUrl); + this.props.onCloseDialog(); + }; shareLinkClicked = () => { navigator.share({ @@ -84,7 +89,8 @@ class InfoDialog extends Component { mailingListEmail: "", mailingListPrivacy: false, copyLinkButtonText: "Copy", - addMediaUrl: "" + addMediaUrl: "", + customSceneUrl: "" }; signUpForMailingList = async e => { @@ -201,7 +207,7 @@ class InfoDialog extends Component { dialogTitle = "Add Media"; dialogBody = ( <div> - <div>Tip: You can paste media urls directly into hubs with ctrl+v</div> + <div>Tip: You can paste media URLs directly into Hubs with ctrl+v</div> <form onSubmit={this.onAddMediaClicked}> <div className="add-media-form"> <input @@ -222,6 +228,31 @@ class InfoDialog extends Component { </div> ); break; + case InfoDialog.dialogTypes.custom_scene: + dialogTitle = "Use Custom Scene"; + dialogBody = ( + <div> + <div>Enter a URL to a GLTF file to use for your scene:</div> + <form onSubmit={this.onCustomSceneClicked}> + <div className="custom-scene-form"> + <input + type="url" + placeholder="URL to Scene GLTF" + className="custom-scene-form__link_field" + value={this.state.customSceneUrl} + onChange={e => this.setState({ customSceneUrl: e.target.value })} + required + /> + <div className="custom-scene-form__buttons"> + <button className="custom-scene-form__action-button"> + <span>Create Room</span> + </button> + </div> + </div> + </form> + </div> + ); + break; case InfoDialog.dialogTypes.updates: dialogTitle = ""; dialogBody = (