From 3fd96ad5c737a110c8450a61c21d7f8f7cf3d554 Mon Sep 17 00:00:00 2001 From: Greg Fodor <gfodor@gmail.com> Date: Fri, 27 Jul 2018 23:29:39 +0000 Subject: [PATCH] WIP on moving media tools into footer --- src/assets/stylesheets/2d-hud.scss | 13 +- src/assets/stylesheets/footer.scss | 123 ------------------ src/assets/stylesheets/info-dialog.scss | 8 +- src/assets/stylesheets/ui-root.scss | 27 +++- src/components/stats-plus.css | 4 +- src/react-components/2d-hud.js | 53 +++----- ...ools-dialog.js => create-object-dialog.js} | 59 +++++---- src/react-components/footer.js | 96 -------------- src/react-components/info-dialog.js | 26 ++-- src/react-components/ui-root.js | 24 ++-- 10 files changed, 112 insertions(+), 321 deletions(-) delete mode 100644 src/assets/stylesheets/footer.scss rename src/react-components/{media-tools-dialog.js => create-object-dialog.js} (51%) delete mode 100644 src/react-components/footer.js diff --git a/src/assets/stylesheets/2d-hud.scss b/src/assets/stylesheets/2d-hud.scss index 475e46159..1109316b6 100644 --- a/src/assets/stylesheets/2d-hud.scss +++ b/src/assets/stylesheets/2d-hud.scss @@ -46,12 +46,6 @@ cursor: pointer; } -:local(.addMediaButton) { - position: absolute; - top: 90px; - background-color: #404040; -} - :local(.iconButton.small) { width: 30px; height: 30px; @@ -101,3 +95,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/footer.scss b/src/assets/stylesheets/footer.scss deleted file mode 100644 index ee9439da0..000000000 --- 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 840742fa3..de78482ea 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; @@ -149,7 +145,6 @@ &__submit { @extend %bottom-button; - border: 0; margin-top: 0; margin-bottom: 0; } @@ -162,6 +157,7 @@ &__privacy_checkbox { @extend %checkbox; vertical-align: middle; + margin-bottom: 18px; } &__privacy_checkbox:checked { diff --git a/src/assets/stylesheets/ui-root.scss b/src/assets/stylesheets/ui-root.scss index a9ac0af11..4c10d1b97 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/components/stats-plus.css b/src/components/stats-plus.css index 80e411dfc..f3850c47b 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/react-components/2d-hud.js b/src/react-components/2d-hud.js index 12e7abc17..15168e999 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,30 @@ 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)}> + <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 e0753b288..e67bb52cf 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 fbc409887..000000000 --- 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 a75cffb0b..a961df45f 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:</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"; @@ -356,14 +357,9 @@ class InfoDialog extends Component { 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 c5214cbae..5352646ed 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -23,6 +23,7 @@ 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 +527,7 @@ class UIRoot extends Component { this.setState({ infoDialogType: null, linkCode: null, linkCodeCancel: null }); }; - handleAddMedia = url => { + handleCreateObject = url => { this.props.scene.emit("add_media", url); }; @@ -851,7 +852,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 +866,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 +886,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,14 +901,7 @@ 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 })} - /> + <Footer onClickCreate={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.create_object })} /> </div> ) : null} </div> -- GitLab