diff --git a/src/assets/hud/create_object-hover.png b/src/assets/hud/create_object-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..3936231f84148e546ce366987e47f4667012f765 Binary files /dev/null and b/src/assets/hud/create_object-hover.png differ diff --git a/src/assets/hud/create_object.png b/src/assets/hud/create_object.png new file mode 100644 index 0000000000000000000000000000000000000000..af63116b547ba073bb97d95ff2e24e3cec5dd0ae Binary files /dev/null and b/src/assets/hud/create_object.png differ diff --git a/src/assets/hud/tooltip.9.png b/src/assets/hud/tooltip.9.png old mode 100755 new mode 100644 diff --git a/src/assets/stylesheets/2d-hud.scss b/src/assets/stylesheets/2d-hud.scss index 475e4615923a6e1359984b1c6ea9be72e9242947..edc8b391990a9c81dd67ca2579ffc2991a169ac3 100644 --- a/src/assets/stylesheets/2d-hud.scss +++ b/src/assets/stylesheets/2d-hud.scss @@ -2,13 +2,20 @@ :local(.container) { position: absolute; - top: 10px; display: flex; justify-content: center; align-items: center; height: 80px; width: 100%; user-select: none; + + &:local(.top) { + top: 10px; + } + + &:local(.bottom) { + bottom: 20px; + } } :local(.panel) { @@ -46,12 +53,6 @@ cursor: pointer; } -:local(.addMediaButton) { - position: absolute; - top: 90px; - background-color: #404040; -} - :local(.iconButton.small) { width: 30px; height: 30px; @@ -101,3 +102,10 @@ :local(.iconButton.freeze.active:hover) { background-image: url(../hud/freeze_on-hover.png); } + +:local(.iconButton.create-object) { + background-image: url(../hud/create_object.png); +} +:local(.iconButton.create-object:hover) { + background-image: url(../hud/create_object-hover.png); +} diff --git a/src/assets/stylesheets/audio.scss b/src/assets/stylesheets/audio.scss index e49dc94e44fbce76251039df38ea93bd8bb8e8f2..f22b609e5f6e1604dffa3f980097f2cf6fd796d7 100644 --- a/src/assets/stylesheets/audio.scss +++ b/src/assets/stylesheets/audio.scss @@ -9,6 +9,7 @@ flex: 1; display: flex; justify-content: center; + align-items: flex-end; } &__enter-button { @extend %bottom-action-button; @@ -16,10 +17,12 @@ &__title { @extend %top-title; + @extend %glass-text; } &__subtitle { @extend %top-subtitle; + @extend %glass-text; } &__device-chooser { @@ -116,10 +119,12 @@ &__title { @extend %top-title; + @extend %glass-text; } &__subtitle { @extend %top-subtitle; + @extend %glass-text; } &__button-container { diff --git a/src/assets/stylesheets/entry.scss b/src/assets/stylesheets/entry.scss index 6dcd4156495a9c3efcfc355de6cd27a6ac9d08ba..5dc16221c4584917a1ae14997f608bb048073bab 100644 --- a/src/assets/stylesheets/entry.scss +++ b/src/assets/stylesheets/entry.scss @@ -53,6 +53,7 @@ :local(.button-container) { margin: auto; + margin-top: -72px; text-align: center; flex: 10; display: flex; @@ -64,6 +65,7 @@ @extend %action-button; align-self: center; width: 75%; + margin-top: 28px; } :local(.presence-info) { diff --git a/src/assets/stylesheets/footer.scss b/src/assets/stylesheets/footer.scss deleted file mode 100644 index ee9439da0cc2755dca67bc69d9acf39a1e0421e4..0000000000000000000000000000000000000000 --- a/src/assets/stylesheets/footer.scss +++ /dev/null @@ -1,123 +0,0 @@ -@import 'shared'; - -:local(.container) { - position: absolute; - width: 100%; - bottom: 0; - font-size: 1.3em; - display: flex; - flex-direction: column; - // Position above virtual gamepad controls on mobile - z-index: 1; - user-select: none; - - @media (min-width: 769px) and (min-height: 421px) { - pointer-events: auto; - } -} - -:local(.floatingButton) { - display: flex; - justify-content: center; -} -:local(.header), :local(.menu-header) { - display: flex; - justify-content: center; -} -:local(.header) { - border-bottom: 1px solid rgba(32, 32, 32, 0.65); - - @media (max-width: 768px) , (max-height: 340px) { - border-bottom: none; - pointer-events: none; - } -} -:local(.menu-header) { - background-color: transparent; - border-bottom: 1px solid rgba(32, 32, 32, 0.65); - - @media (min-width: 769px) , (max-height: 421px) { - display: none; - } -} -:local(.header) { - background-color: rgba(0, 0, 0, 0.65); - - @media (max-width: 768px) , (max-height: 420px) { - background-color: transparent; - } - - :local(.hub-info) { - @media (max-width: 768px) , (max-height: 420px) { - display: none; - } - } - - :local(.hub-stats) { - @media (max-width: 768px) , (max-height: 420px) { - display: none; - } - } -} - -:local(.hub-info) { - flex: 1; - margin: 16px 24px; - display: flex; - align-items: center; - @media (max-width: 768px) , (max-height: 420px) { - margin: 16px 8px; - margin-left: 24px; - font-size: 0.9em; - } -} -:local(.hub-stats) { - text-align: right; - margin: 16px 24px; - display: flex; - align-items: center; - justify-content: flex-end; - @media (min-width: 769px) and (min-height: 421px) { - flex: 1; - } - @media (max-width: 768px) , (max-height: 420px) { - margin: 16px 8px; - } - :local(.hub-participant-count) { - margin: 0 12px; - } -} - -:local(.menu) { - padding: 5px 0; - background-color: rgba(0, 0, 0, 0.85); - display: flex; - flex-direction: column; -} -:local(.menu-buttons) { - margin: 0 auto; -} -:local(.menu-button) { - @extend %fa-icon-button; - pointer-events: auto; - - :local(.menu-button__icon) { - @extend %fa-icon-button-icon; - } - - :local(.menu-button__text) { - @extend %fa-icon-button-text; - } - - :local(.menu-button__narrow-close-icon) { - @media (max-width: 768px) , (max-height: 420px) { - display: none; - } - } - - :local(.menu-button__wide-close-icon) { - @media (min-width: 769px) and (min-height: 421px) { - display: none; - } - } -} diff --git a/src/assets/stylesheets/info-dialog.scss b/src/assets/stylesheets/info-dialog.scss index 840742fa3d285c87654496bfcc0bc6429a051f47..953f375a695902a3f6daa7af38b531c1f0dcbeb5 100644 --- a/src/assets/stylesheets/info-dialog.scss +++ b/src/assets/stylesheets/info-dialog.scss @@ -11,15 +11,11 @@ .dialog { display: grid; grid-template-columns: 1fr 20px minmax(200px, 525px) 20px 1fr; - grid-template-rows: 1fr 20px 360px 20px 1fr; + grid-template-rows: 1fr 20px minmax(200px, max-content) 20px 1fr; width: 100%; height: 100%; background-color: rgba(0,0,0,.2); - &--tall { - grid-template-rows: 1fr 20px minmax(200px, max-content) 20px 1fr; - } - &__box { grid-column: 3; grid-row: 3; @@ -49,6 +45,7 @@ flex-direction: column; height: 100%; overflow-y: auto; + margin: 10px 0; a { color: white } } @@ -149,7 +146,6 @@ &__submit { @extend %bottom-button; - border: 0; margin-top: 0; margin-bottom: 0; } @@ -162,6 +158,7 @@ &__privacy_checkbox { @extend %checkbox; vertical-align: middle; + margin-bottom: 18px; } &__privacy_checkbox:checked { diff --git a/src/assets/stylesheets/shared.scss b/src/assets/stylesheets/shared.scss index b456c5c4e581b2dae15fbb5e1c947f815db91c22..1e52392c3cd57c4704d7822c3f31e8adf79c086c 100644 --- a/src/assets/stylesheets/shared.scss +++ b/src/assets/stylesheets/shared.scss @@ -52,8 +52,7 @@ $darkest-grey: rgba(32, 32, 32, 1.0); %bottom-button { @extend %action-button; - margin-top: auto; - margin-bottom: 24px; + margin: 16px 0; } %action-button { @@ -63,6 +62,7 @@ $darkest-grey: rgba(32, 32, 32, 1.0); cursor: pointer; border: 3px solid white; border-radius: 26px; + height: 64px; padding: 12px; background: #2F80ED; font-size: 1.3em; diff --git a/src/assets/stylesheets/ui-root.scss b/src/assets/stylesheets/ui-root.scss index a9ac0af1154cd3f5d50f084cf570b82de7e74ea1..4c10d1b97c30efbb3768064e4543325503e5c033 100644 --- a/src/assets/stylesheets/ui-root.scss +++ b/src/assets/stylesheets/ui-root.scss @@ -74,7 +74,7 @@ :local(.invite-nag-button) { position: absolute; - top: 130px; + top: 110px; left: 0; width: 100%; display: flex; @@ -88,3 +88,28 @@ pointer-events: auto; } } + +:local(.presence-info) { + text-align: right; + position: absolute; + top: 0; + right: 16px; + margin: 16px 0; + display: flex; + align-items: center; + justify-content: flex-end; + font-size: 1.3em; + text-shadow: 0px 0px 4px rgba(0, 0, 0, 1.0); + -webkit-filter: drop-shadow( 0px 0px 3px #606060 ); + filter: drop-shadow( 0px 0px 3px #606060 ); + + @media (min-width: 769px) and (min-height: 421px) { + flex: 1; + } + @media (max-width: 768px) , (max-height: 420px) { + margin: 16px 8px; + } + :local(.occupant-count) { + margin: 0 12px; + } +} diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index aaefa3e3de60040413bb250b2d621b8e13866d48..c326deb1b1555bbd246c1e33ffb050af23ef1811 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -4,16 +4,16 @@ "entry.desktop-screen": "Screen", "entry.mobile-screen": "Phone", "entry.mobile-safari": "Safari", - "entry.generic-prefix": "Enter in ", - "entry.generic-medium": "VR", + "entry.generic-prefix": "Enter with ", + "entry.generic-medium": "PC VR", "entry.generic-subtitle-desktop": "Oculus or SteamVR", "entry.gearvr-prefix": "Enter on ", "entry.gearvr-medium": "Gear VR", - "entry.device-prefix-desktop": "Send to ", - "entry.device-prefix-mobile": "Enter on ", - "entry.device-medium": "Device", - "entry.device-subtitle-desktop": "Standalone Headset or Phone", - "entry.device-subtitle-mobile": "Mobile Headset or PC", + "entry.device-prefix-desktop": "Use a ", + "entry.device-prefix-mobile": "Use a ", + "entry.device-medium": "Mobile Headset", + "entry.device-subtitle-desktop": "Standalone or Phone Clip-in", + "entry.device-subtitle-mobile": "Standalone or Phone Clip-in", "entry.device-subtitle-vr": "Phone or PC", "entry.cardboard": "Enter on Google Cardboard", "entry.daydream-prefix": "Enter on ", @@ -65,12 +65,13 @@ "home.have_entry_code": "have a link code?", "mailing_list.privacy_label": "I'm okay with Mozilla handling my info as explained in", "mailing_list.privacy_link": "this Privacy Notice", - "link.in_your_browser": "In your device's browser, go to:", + "link.in_your_browser": "In your headset's browser, go to:", "link.enter_code": "Then, enter this link code:", "link.do_not_close": "Keep this dialog open to use this code.", "link.link_page_header": "Enter your code:", "link.dont_have_a_code": "Don't have a code?", "link.create_a_room": "Create a Room", - "link.try_again": "We couldn't find that code. Please try again." + "link.try_again": "We couldn't find that code. Please try again.", + "help.report_issue": "Report an Issue" } } diff --git a/src/components/stats-plus.css b/src/components/stats-plus.css index 80e411dfc77fce5109655aefd2e0347d59a01cda..f3850c47b80cbc9f456c30f9ef1a1e7b17fb500f 100644 --- a/src/components/stats-plus.css +++ b/src/components/stats-plus.css @@ -14,8 +14,8 @@ font-family: monospace; cursor: pointer; position: absolute; - top: 10px; - right: 0; + top: 40px; + right: 16px; padding: 8px 12px; color: #aaa; font-size: 10px; diff --git a/src/index.js b/src/index.js index f61c18337e40519a46693bf09607c105be58efae..7fddba12131a6d46fea444a42b2001acfe7afeb7 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,9 @@ registerTelemetry(); ReactDOM.render( <HomeRoot initialEnvironment={qs.get("initial_environment")} - dialogType={qs.has("list_signup") ? InfoDialog.dialogTypes.updates : null} + dialogType={ + qs.has("list_signup") ? InfoDialog.dialogTypes.updates : qs.has("report") ? InfoDialog.dialogTypes.report : null + } />, document.getElementById("home-root") ); diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js index 12e7abc171ac4c3e4c861e40e70cb08788661076..d017dcad47918a476931237eb453fa5d1971a8a8 100644 --- a/src/react-components/2d-hud.js +++ b/src/react-components/2d-hud.js @@ -4,27 +4,8 @@ import cx from "classnames"; import styles from "../assets/stylesheets/2d-hud.scss"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus"; - -const qs = new URLSearchParams(location.search); -function qsTruthy(param) { - const val = qs.get(param); - // if the param exists but is not set (e.g. "?foo&bar"), its value is the empty string. - return val === "" || /1|on|true/i.test(val); -} -const enableMediaTools = qsTruthy("mediaTools"); - -const TwoDHUD = ({ - muted, - frozen, - spacebubble, - onToggleMute, - onToggleFreeze, - onToggleSpaceBubble, - onClickAddMedia -}) => ( - <div className={styles.container}> +const TopHUD = ({ muted, frozen, spacebubble, onToggleMute, onToggleFreeze, onToggleSpaceBubble }) => ( + <div className={cx(styles.container, styles.top)}> <div className={cx("ui-interactive", styles.panel, styles.left)}> <div className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })} @@ -44,26 +25,32 @@ const TwoDHUD = ({ onClick={onToggleSpaceBubble} /> </div> - {enableMediaTools ? ( - <div - className={cx("ui-interactive", styles.iconButton, styles.small, styles.addMediaButton)} - title="Add Media" - onClick={onClickAddMedia} - > - <FontAwesomeIcon icon={faPlus} /> - </div> - ) : null} </div> ); -TwoDHUD.propTypes = { +TopHUD.propTypes = { muted: PropTypes.bool, frozen: PropTypes.bool, spacebubble: PropTypes.bool, onToggleMute: PropTypes.func, onToggleFreeze: PropTypes.func, - onToggleSpaceBubble: PropTypes.func, - onClickAddMedia: PropTypes.func + onToggleSpaceBubble: PropTypes.func +}; + +const BottomHUD = ({ onCreateObject }) => ( + <div className={cx(styles.container, styles.bottom)}> + {new URLSearchParams(document.location.search).get("mediaTools") === "true" && ( + <div + className={cx("ui-interactive", styles.iconButton, styles.large, styles.createObject)} + title={"Create Object"} + onClick={onCreateObject} + /> + )} + </div> +); + +BottomHUD.propTypes = { + onCreateObject: PropTypes.func }; -export default TwoDHUD; +export default { TopHUD, BottomHUD }; diff --git a/src/react-components/media-tools-dialog.js b/src/react-components/create-object-dialog.js similarity index 51% rename from src/react-components/media-tools-dialog.js rename to src/react-components/create-object-dialog.js index e0753b2882b9462e44a49023df9171822fbccf02..e67bb52cf1010176cc083054275afcfca44737d7 100644 --- a/src/react-components/media-tools-dialog.js +++ b/src/react-components/create-object-dialog.js @@ -7,63 +7,70 @@ const attributionHostnames = { "giphy.com": giphyLogo }; -let lastAddMediaUrl = ""; -export default class MediaToolsDialog extends Component { +const DEFAULT_OBJECT_URL = "https://asset-bundles-prod.reticulum.io/interactables/Ducky/DuckyMesh-438ff8e022.gltf"; + +let lastUrl = ""; + +export default class CreateObjectDialog extends Component { state = { - addMediaUrl: "" + url: "" }; static propTypes = { - onAddMedia: PropTypes.func, + onCreateObject: PropTypes.func, onCloseDialog: PropTypes.func }; - constructor() { - super(); - this.onAddMediaClicked = this.onAddMediaClicked.bind(this); - this.onUrlChange = this.onUrlChange.bind(this); - } - componentDidMount() { - this.setState({ addMediaUrl: lastAddMediaUrl }, () => { + this.setState({ url: lastUrl }, () => { this.onUrlChange({ target: this.input }); }); } componentWillUnmount() { - lastAddMediaUrl = this.state.addMediaUrl; + lastUrl = this.state.url; } - onUrlChange(e) { - this.setState({ - addMediaUrl: e.target.value, - attributionImage: e.target.validity.valid && attributionHostnames[new URL(e.target.value).hostname] - }); - } + onUrlChange = e => { + if (e && e.target.value && e.target.value !== "") { + this.setState({ + url: e.target.value, + attributionImage: e.target.validity.valid && attributionHostnames[new URL(e.target.value).hostname] + }); + } + }; - onAddMediaClicked() { - this.props.onAddMedia(this.state.addMediaUrl); + onCreateClicked = () => { + this.props.onCreateObject(this.state.url || DEFAULT_OBJECT_URL); this.props.onCloseDialog(); - } + }; render() { return ( <div> - <div>Tip: You can paste media urls directly into hubs with ctrl+v</div> - <form onSubmit={this.onAddMediaClicked}> + {!AFRAME.utils.device.isMobile() ? ( + <div> + Paste a URL from the web to create an object in the room. + <br /> + Tip: You can paste directly into Hubs using Ctrl+V + </div> + ) : ( + <div /> + )} + + <form onSubmit={this.onCreateClicked}> <div className="add-media-form"> <input ref={el => (this.input = el)} type="url" placeholder="Image, Video, or GLTF URL" className="add-media-form__link_field" - value={this.state.addMediaUrl} + value={this.state.url} onChange={this.onUrlChange} - required /> <div className="add-media-form__buttons"> <button className="add-media-form__action-button"> - <span>Add</span> + <span>create</span> </button> </div> {this.state.attributionImage ? ( diff --git a/src/react-components/footer.js b/src/react-components/footer.js deleted file mode 100644 index fbc40988748fba5a238f0fe807d115468cd79f38..0000000000000000000000000000000000000000 --- a/src/react-components/footer.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faUsers } from "@fortawesome/free-solid-svg-icons/faUsers"; -import { faBars } from "@fortawesome/free-solid-svg-icons/faBars"; -import { faShareAlt } from "@fortawesome/free-solid-svg-icons/faShareAlt"; -import { faExclamation } from "@fortawesome/free-solid-svg-icons/faExclamation"; -import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes"; -import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown"; -import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion"; -import { faNewspaper } from "@fortawesome/free-solid-svg-icons/faNewspaper"; - -import styles from "../assets/stylesheets/footer.scss"; - -export default class Footer extends Component { - static propTypes = { - hubName: PropTypes.string, - occupantCount: PropTypes.number, - onClickInvite: PropTypes.func, - onClickReport: PropTypes.func, - onClickUpdates: PropTypes.func, - onClickHelp: PropTypes.func - }; - state = { - menuVisible: false - }; - render() { - const menuVisible = this.state.menuVisible; - return ( - <div className={styles.container}> - <div className={styles.header}> - <div className={styles.hubInfo}> - <span>{this.props.hubName}</span> - </div> - <button className={styles.menuButton} onClick={() => this.setState({ menuVisible: !menuVisible })}> - <i className={styles.menuButtonIcon}> - <FontAwesomeIcon className={styles.menuButtonNarrowCloseIcon} icon={menuVisible ? faArrowDown : faBars} /> - <FontAwesomeIcon className={styles.menuButtonWideCloseIcon} icon={menuVisible ? faTimes : faBars} /> - </i> - </button> - <div className={styles.hubStats}> - <FontAwesomeIcon icon={faUsers} /> - <span className={styles.hubParticipantCount}>{this.props.occupantCount || "-"}</span> - </div> - </div> - {menuVisible && ( - <div className={styles.menu}> - <div className={styles.menuHeader}> - <div className={styles.hubInfo}> - <span>{this.props.hubName}</span> - </div> - <div className={styles.hubStats}> - <FontAwesomeIcon icon={faUsers} /> - <span className={styles.hubParticipantCount}>{this.props.occupantCount || "-"}</span> - </div> - </div> - <div className={styles.menuButtons}> - <button className={styles.menuButton} onClick={this.props.onClickInvite}> - <i className={styles.menuButtonIcon}> - <FontAwesomeIcon icon={faShareAlt} /> - </i> - <span className={styles.menuButtonText}> - <strong>Invite</strong> Friends - </span> - </button> - <button className={styles.menuButton} onClick={this.props.onClickHelp}> - <i className={styles.menuButtonIcon}> - <FontAwesomeIcon icon={faQuestion} /> - </i> - <span className={styles.menuButtonText}> - <strong>Learn</strong> the Basics - </span> - </button> - <button className={styles.menuButton} onClick={this.props.onClickUpdates}> - <i className={styles.menuButtonIcon}> - <FontAwesomeIcon icon={faNewspaper} /> - </i> - <span className={styles.menuButtonText}> - <strong>Sign Up</strong> for Updates - </span> - </button> - <button className={styles.menuButton} onClick={this.props.onClickReport}> - <i className={styles.menuButtonIcon}> - <FontAwesomeIcon icon={faExclamation} /> - </i> - <span className={styles.menuButtonText}> - <strong>Report</strong> an Issue - </span> - </button> - </div> - </div> - )} - </div> - ); - } -} diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js index a75cffb0b6854a6e784d2927a98c058681a7d70c..83c7d219d8b907798871a68efd63c1709a8c1125 100644 --- a/src/react-components/info-dialog.js +++ b/src/react-components/info-dialog.js @@ -1,11 +1,10 @@ import React, { Component } from "react"; import copy from "copy-to-clipboard"; -import classNames from "classnames"; import PropTypes from "prop-types"; import { FormattedMessage } from "react-intl"; import formurlencoded from "form-urlencoded"; import LinkDialog from "./link-dialog.js"; -import MediaToolsDialog from "./media-tools-dialog.js"; +import CreateObjectDialog from "./create-object-dialog.js"; const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$"; // TODO i18n @@ -21,14 +20,14 @@ class InfoDialog extends Component { help: Symbol("help"), link: Symbol("link"), webvr_recommend: Symbol("webvr_recommend"), - add_media: Symbol("add_media"), + create_object: Symbol("create_object"), custom_scene: Symbol("custom_scene") }; static propTypes = { dialogType: PropTypes.oneOf(Object.values(InfoDialog.dialogTypes)), onCloseDialog: PropTypes.func, onSubmittedEmail: PropTypes.func, - onAddMedia: PropTypes.func, + onCreateObject: PropTypes.func, onCustomScene: PropTypes.func, linkCode: PropTypes.string }; @@ -83,7 +82,7 @@ class InfoDialog extends Component { mailingListEmail: "", mailingListPrivacy: false, copyLinkButtonText: "copy", - addMediaUrl: "", + createObjectUrl: "", customRoomName: "", customSceneUrl: "" }; @@ -147,7 +146,7 @@ class InfoDialog extends Component { dialogTitle = "Invite Others"; dialogBody = ( <div> - <div>Just share the link to invite others.</div> + <div>Just share the link and they'll join you:</div> <div className="invite-form"> <input type="text" @@ -198,9 +197,11 @@ class InfoDialog extends Component { </div> ); break; - case InfoDialog.dialogTypes.add_media: - dialogTitle = "Add Media"; - dialogBody = <MediaToolsDialog onAddMedia={this.props.onAddMedia} onCloseDialog={this.props.onCloseDialog} />; + case InfoDialog.dialogTypes.create_object: + dialogTitle = "Create Object"; + dialogBody = ( + <CreateObjectDialog onCreateObject={this.props.onCreateObject} onCloseDialog={this.props.onCloseDialog} /> + ); break; case InfoDialog.dialogTypes.custom_scene: dialogTitle = "Create a Room"; @@ -329,6 +330,9 @@ class InfoDialog extends Component { > <FormattedMessage id="profile.privacy_notice" /> </a> + <a target="_blank" rel="noopener noreferrer" href="/?report"> + <FormattedMessage id="help.report_issue" /> + </a> </p> </div> ); @@ -351,19 +355,14 @@ class InfoDialog extends Component { ); break; case InfoDialog.dialogTypes.link: - dialogTitle = "Send Link to Device"; + dialogTitle = "Open on Headset"; dialogBody = <LinkDialog linkCode={this.props.linkCode} />; break; } - const dialogClasses = classNames({ - dialog: true, - "dialog--tall": this.props.dialogType === InfoDialog.dialogTypes.help - }); - return ( <div className="dialog-overlay"> - <div className={dialogClasses} onClick={this.onContainerClicked}> + <div className="dialog" onClick={this.onContainerClicked}> <div className="dialog__box"> <div className="dialog__box__contents"> <button className="dialog__box__contents__close" onClick={this.props.onCloseDialog}> diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index c5214cbae016067e59beb8fca459208d636e6a73..f1584d683065f92286eef30f574b925d0a532df8 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -22,7 +22,7 @@ import { ProfileInfoHeader } from "./profile-info-header.js"; import ProfileEntryPanel from "./profile-entry-panel"; import InfoDialog from "./info-dialog.js"; import TwoDHUD from "./2d-hud"; -import Footer from "./footer"; +import { faUsers } from "@fortawesome/free-solid-svg-icons/faUsers"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion"; @@ -526,7 +526,7 @@ class UIRoot extends Component { this.setState({ infoDialogType: null, linkCode: null, linkCodeCancel: null }); }; - handleAddMedia = url => { + handleCreateObject = url => { this.props.scene.emit("add_media", url); }; @@ -643,12 +643,6 @@ class UIRoot extends Component { <span className={entryStyles.people}>2 people</span> have joined </div> )} - <button - className={entryStyles.inviteButton} - onClick={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.invite })} - > - <FormattedMessage id="entry.invite-others" /> - </button> {this.props.availableVREntryTypes.screen === VR_DEVICE_AVAILABILITY.yes && ( <TwoDEntryButton onClick={this.enter2D} /> )} @@ -675,6 +669,12 @@ class UIRoot extends Component { </div> )} {screenSharingCheckbox} + <button + className={entryStyles.inviteButton} + onClick={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.invite })} + > + <FormattedMessage id="entry.invite-others" /> + </button> </div> </div> ) : null; @@ -851,7 +851,7 @@ class UIRoot extends Component { linkCode={this.state.linkCode} onSubmittedEmail={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.email_submitted })} onCloseDialog={this.handleCloseDialog} - onAddMedia={this.handleAddMedia} + onCreateObject={this.handleCreateObject} /> {this.state.entryStep === ENTRY_STEPS.finished && ( @@ -865,6 +865,13 @@ class UIRoot extends Component { </button> )} + {this.state.entryStep === ENTRY_STEPS.finished && ( + <div className={styles.presenceInfo}> + <FontAwesomeIcon icon={faUsers} /> + <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span> + </div> + )} + <div className="ui-dialog"> {(this.state.entryStep !== ENTRY_STEPS.finished || this.isWaitingForAutoExit()) && ( <div className={dialogBoxClassNames}> @@ -878,14 +885,13 @@ class UIRoot extends Component { </div> {this.state.entryStep === ENTRY_STEPS.finished ? ( <div> - <TwoDHUD + <TwoDHUD.TopHUD muted={this.state.muted} frozen={this.state.frozen} spacebubble={this.state.spacebubble} onToggleMute={this.toggleMute} onToggleFreeze={this.toggleFreeze} onToggleSpaceBubble={this.toggleSpaceBubble} - onClickAddMedia={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.add_media })} /> {this.props.occupantCount <= 1 && ( <div className={styles.inviteNagButton}> @@ -894,13 +900,8 @@ class UIRoot extends Component { </button> </div> )} - <Footer - hubName={this.props.hubName} - occupantCount={this.props.occupantCount} - onClickInvite={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.invite })} - onClickReport={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.report })} - onClickHelp={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.help })} - onClickUpdates={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.updates })} + <TwoDHUD.BottomHUD + onCreateObject={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.create_object })} /> </div> ) : null} diff --git a/src/utils/vr-caps-detect.js b/src/utils/vr-caps-detect.js index 3cea1c2e3029fa3354eaa3d07ecaba82b0438580..3294820125788e0343eb5f70277eefc48b75a2c2 100644 --- a/src/utils/vr-caps-detect.js +++ b/src/utils/vr-caps-detect.js @@ -50,8 +50,6 @@ export async function getAvailableVREntryTypes() { // This needs to be kept up-to-date with the latest browsers that can support VR and Hubs. // Checking for navigator.getVRDisplays always passes b/c of polyfill. const isWebVRCapableBrowser = window.hasNativeWebVRImplementation; - const isFirefoxReality = window.orientation === 0 && "buildID" in navigator && isWebVRCapableBrowser; - const isInHMD = isOculusBrowser || isFirefoxReality; const isDaydreamCapableBrowser = !!(isWebVRCapableBrowser && browser.name === "chrome" && !isSamsungBrowser); const isIDevice = AFRAME.utils.device.isIOS(); @@ -64,13 +62,23 @@ export async function getAvailableVREntryTypes() { : VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.no; + const displays = isWebVRCapableBrowser ? await navigator.getVRDisplays() : []; + const isFirefoxReality = window.orientation === 0 && "buildID" in navigator && displays.length > 0; + const isInHMD = isOculusBrowser || isFirefoxReality; + const screen = isInHMD ? VR_DEVICE_AVAILABILITY.no : isIDevice && isUIWebView ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.yes; - let generic = AFRAME.utils.device.isMobile() ? VR_DEVICE_AVAILABILITY.no : VR_DEVICE_AVAILABILITY.maybe; + // HACK -- we prompt the user to install firefox if they click the VR button on a non-WebVR compatible + // browser. once we change this (ie when Chrome has VR support) then this check can be removed. Without this + // check if you have FF on Mac/Linux you'll get the confusing flow of having a VR button but then + // being asked to download FF. + const isNonWebVRFirefox = !isWebVRCapableBrowser && isFirefoxBrowser; + let generic = + AFRAME.utils.device.isMobile() || isNonWebVRFirefox ? VR_DEVICE_AVAILABILITY.no : VR_DEVICE_AVAILABILITY.maybe; let cardboard = VR_DEVICE_AVAILABILITY.no; // We only consider GearVR support as "maybe" and never "yes". The only browser @@ -89,8 +97,6 @@ export async function getAvailableVREntryTypes() { isMaybeDaydreamCompatibleDevice(ua) && !isInHMD ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.no; if (isWebVRCapableBrowser) { - const displays = await navigator.getVRDisplays(); - // Generic is supported for non-blacklisted devices and presentable HMDs. generic = displays.find( d => d.capabilities.canPresent && !GENERIC_ENTRY_TYPE_DEVICE_BLACKLIST.find(r => r.test(d.displayName))