diff --git a/src/assets/stylesheets/create-object-dialog.scss b/src/assets/stylesheets/create-object-dialog.scss new file mode 100644 index 0000000000000000000000000000000000000000..9486b858b53faf685566f97ac1867629fef210db --- /dev/null +++ b/src/assets/stylesheets/create-object-dialog.scss @@ -0,0 +1,61 @@ +@import 'shared'; + +:local(.smallButton) { + padding-right: 5%; + font-size: 2em; + align-self: center; +} + +:local(.cancelIcon) { + padding-left: 2%; + color: white; + :hover { + color: #FF3D7F + } +} + +:local(.uploadIcon) { + color: white; + :hover { + color: #2F80ED; + } +} + +:local(.inputBorder) { + margin: 2% 1% 2% 1%; + display: flex; + width: 100%; + height: 60px; + justify-content: space-between; + @extend %rounded-border; + @extend %default-font; +} + +:local(.urlInput) { + border: none; + text-align: center; + font-size: 1.3em; + padding-left: 8%; + padding-right: 0%; +} + +:local(.fileNameLabel) { + border: none; + text-align: center; + align-self: center; + padding-left: 8%; + padding-right: 0%; +} + +:local(.form) { + width: 80%; +} + +:local(.hideFileInput) { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} diff --git a/src/assets/stylesheets/info-dialog.scss b/src/assets/stylesheets/info-dialog.scss index 556b78524658edd1ab924f1e481e4c1bd26dfe29..11c79e6de8bb8a08692be706a96b4216c77ca894 100644 --- a/src/assets/stylesheets/info-dialog.scss +++ b/src/assets/stylesheets/info-dialog.scss @@ -75,106 +75,6 @@ } } -.input-border { - margin: 2% 1% 2% 1%; - display: flex; - width: 100%; - justify-content: space-between; - background:white; - @extend %rounded-border; - @extend %default-font; -} - -.small-button { - width: 8%; - padding-right: 2%; - padding-left: 2%; -} - -.url-input { - border: none; - padding-left: 2%; - flex-grow: 1; - text-align: center; - background:transparent; - color:black; - font-size: 1.1em; -} - -.file-name { - text-align: center; - flex-grow: 1; - align-self: center; - color: black; -} - -.form { - width: 80%; -} - -.hide-file-input { - width: 0.1px; - height: 0.1px; - opacity: 0; - overflow: hidden; - position: absolute; - z-index: -1; -} - -#upload-svg { - fill: #222222; -} - -#upload-svg.unhover { - animation-name: upload-button-unhover; - animation-duration: 0.5s; - animation-timing-function: ease-in; - animation-fill-mode: forwards; -} - -#upload-svg.hover{ - animation-name: upload-button-hover; - animation-duration: 0.2s; - animation-timing-function: ease-out; - animation-fill-mode: forwards; -} - -#cancel-svg { - fill: #FF9E9D; -} - -#cancel-svg.unhover{ - animation-name: cancel-button-unhover; - animation-duration: 0.5s; - animation-timing-function: ease-in; - animation-fill-mode: forwards; -} - -#cancel-svg.hover{ - animation-name: cancel-button-hover; - animation-duration: 0.2s; - animation-timing-function: ease-out; - animation-fill-mode: forwards; -} - -@keyframes upload-button-hover { - from { fill : #222222;} - to {fill : #c431bd;} -} -@keyframes upload-button-unhover { - from {fill : #c431bd;} - to { fill : #222222;} -} - -@keyframes cancel-button-hover { - from { fill : #FF9E9D;} - to {fill : #FF3D7F;} -} -@keyframes cancel-button-unhover { - from {fill : #FF3D7F;} - to { fill : #FF9E9D;} -} - .invite-form, .add-media-form, .custom-scene-form { display: flex; flex-direction: column; @@ -201,7 +101,7 @@ background-color: transparent; line-height: 2.0em; padding-left: 1.25em; - padding-right: 2.25em; + padding-right: 1.25em; margin: 0.5em 0; width: 100%; } diff --git a/src/hub.js b/src/hub.js index ac0acb54b32ee72454010a78e36ff989d79f2eb8..860ca7dee035fb671ae3d1be436d5fe702c9dccc 100644 --- a/src/hub.js +++ b/src/hub.js @@ -83,7 +83,7 @@ import HubChannel from "./utils/hub-channel"; import LinkChannel from "./utils/link-channel"; import { connectToReticulum } from "./utils/phoenix-utils"; import { disableiOSZoom } from "./utils/disable-ios-zoom"; -import { addMedia} from "./utils/media-utils"; +import { addMedia } from "./utils/media-utils"; import "./systems/personal-space-bubble"; import "./systems/app-mode"; @@ -303,7 +303,7 @@ const onReady = async () => { }); const offset = { x: 0, y: 0, z: -1.5 }; - const spawnMediaInfrontOfPlayer = async (src) => { + const spawnMediaInfrontOfPlayer = src => { const entity = addMedia(src, true); entity.setAttribute("offset-relative-to", { target: "#player-camera", diff --git a/src/react-components/create-object-dialog.js b/src/react-components/create-object-dialog.js index 6b5d7a9cc6aa5e54b9f74852d971aefb8f832a7b..d82a4b2235d3eb9b0d10e68c4cf7380cf5b81e90 100644 --- a/src/react-components/create-object-dialog.js +++ b/src/react-components/create-object-dialog.js @@ -1,10 +1,14 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; - import giphyLogo from "../assets/images/giphy_logo.png"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPaperclip, faTimes } from "@fortawesome/free-solid-svg-icons"; +import styles from "../assets/stylesheets/create-object-dialog.scss"; +import cx from "classnames"; const attributionHostnames = { - "giphy.com": giphyLogo + "giphy.com": giphyLogo, + "media.giphy.com": giphyLogo }; const DEFAULT_OBJECT_URL = "https://asset-bundles-prod.reticulum.io/interactables/Ducky/DuckyMesh-438ff8e022.gltf"; @@ -21,6 +25,7 @@ const desktopInstructions = ( ); let lastUrl = ""; +const fileInputId = "file-input"; export default class CreateObjectDialog extends Component { state = { @@ -49,6 +54,7 @@ export default class CreateObjectDialog extends Component { } catch (_) { this.setState({ url: e.target && e.target.value + // Not a valid URL, so don't try to set attributeImage using url.hostname }); return; } @@ -79,37 +85,21 @@ export default class CreateObjectDialog extends Component { }); }; - onHover = e => { - e.currentTarget.children[0].classList.add("hover"); - e.currentTarget.children[0].classList.remove("unhover"); - }; - - onHoverExit = e => { - e.currentTarget.children[0].classList.remove("hover"); - e.currentTarget.children[0].classList.add("unhover"); - }; - render() { - const withContent = ( - <label className="small-button" onClick={this.reset} onMouseEnter={this.onHover} onMouseLeave={this.onHoverExit}> - <svg id="cancel-svg" viewBox="0 0 512 512"> - /* font awesome : times-circle-regular*/ - <path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z" /> - </svg> + const cancelButton = ( + <label className={cx(styles.smallButton, styles.cancelIcon)} onClick={this.reset}> + <FontAwesomeIcon icon={faTimes} /> </label> ); - const withoutContent = ( - <label htmlFor="file-input" className="small-button" onMouseEnter={this.onHover} onMouseLeave={this.onHoverExit}> - <svg id="upload-svg" viewBox="0 0 512 512"> - /* font awesome : upload-solid*/ - <path d="M296 384h-80c-13.3 0-24-10.7-24-24V192h-87.7c-17.8 0-26.7-21.5-14.1-34.1L242.3 5.7c7.5-7.5 19.8-7.5 27.3 0l152.2 152.2c12.6 12.6 3.7 34.1-14.1 34.1H320v168c0 13.3-10.7 24-24 24zm216-8v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h136v8c0 30.9 25.1 56 56 56h80c30.9 0 56-25.1 56-56v-8h136c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z" /> - </svg> + const uploadButton = ( + <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}> + <FontAwesomeIcon icon={faPaperclip} /> </label> ); - const fileName = <label className="file-name">{this.state.fileName}</label>; + const filenameLabel = (<label className={cx(styles.fileNameLabel, "add-media-form__link_field")}>{this.state.fileName}</label>); const urlInput = ( <input - className="url-input" + className={cx(styles.urlInput, "add-media-form__link_field")} placeholder="Image/Video/glTF URL" onChange={this.onUrlChange} value={this.state.url} @@ -121,10 +111,10 @@ export default class CreateObjectDialog extends Component { {isMobile ? mobileInstructions : desktopInstructions} <form onSubmit={this.onCreateClicked}> <div className="add-media-form"> - <input id="file-input" className="hide-file-input" type="file" onChange={this.onFileChange} /> - <div className="input-border"> - {this.state.file ? fileName : urlInput} - {this.state.url || this.state.fileName ? withContent : withoutContent} + <input id={fileInputId} className={styles.hideFileInput} type="file" onChange={this.onFileChange} /> + <div className={styles.inputBorder}> + {this.state.file ? filenameLabel : urlInput} + {this.state.url || this.state.fileName ? cancelButton : uploadButton} </div> <div className="add-media-form__buttons"> <button className="add-media-form__action-button"> diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js index b21095e772d09954a4e880d31def490c2efad233..7049d7c9a3266b7520e9788eedd1897245e56c18 100644 --- a/src/utils/media-utils.js +++ b/src/utils/media-utils.js @@ -22,6 +22,21 @@ export const resolveMedia = async url => { return resolved; }; +export const upload = file => { + const formData = new FormData(); + formData.append("media", file); + return fetch(mediaAPIEndpoint, { + method: "POST", + body: formData + + // We do NOT specify a Content-Type header like so + // headers: { "Content-Type" : "multipart/form-data" }, + // because we want the browser to automatically add + // "Content-Type" : "multipart/form-data; boundary=...--------------<boundary_size>", + // See https://stanko.github.io/uploading-files-using-fetch-multipart-form-data/ for details. + }).then(r => r.json()); +}; + let interactableId = 0; export const addMedia = (src, resize = false) => { const scene = AFRAME.scenes[0]; @@ -33,7 +48,7 @@ export const addMedia = (src, resize = false) => { scene.appendChild(entity); if (typeof src === "object") { - const uploadResponse = upload(src).then(response => { + upload(src).then(response => { const src = response.raw; const contentType = response.meta.expected_content_type; const token = response.meta.access_token; @@ -42,18 +57,3 @@ export const addMedia = (src, resize = false) => { } return entity; }; - -export const upload = file => { - const formData = new FormData(); - formData.append("media", file); - return fetch(mediaAPIEndpoint, { - method: "POST", - body: formData - - // We do NOT specify a Content-Type header like so - // headers: { "Content-Type" : "multipart/form-data" }, - // because we want the browser to automatically add - // "Content-Type" : "multipart/form-data; boundary=...--------------<boundary_size>", - // See https://stanko.github.io/uploading-files-using-fetch-multipart-form-data/ for details. - }).then(r => r.json()); -};