Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Room UI Redesign: Toolbar Buttons / Menus #3206

Merged
merged 14 commits into from
Oct 27, 2020
5 changes: 4 additions & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ module.exports = {
template: require("../src/react-components/icons/IconTemplate"),
svgoConfig: {
plugins: {
removeViewBox: false
removeViewBox: false,
mergePaths: false,
convertShapeToPath: false,
removeHiddenElems: false
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions src/components/mute-mic.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { SOUND_TOGGLE_MIC } from "../systems/sound-effects-system";

const bindAllEvents = function(elements, events, f) {
if (!elements || !elements.length) return;
for (const el of elements) {
Expand Down Expand Up @@ -55,7 +53,6 @@ AFRAME.registerComponent("mute-mic", {
if (!NAF.connection.adapter) return;
if (!this.el.sceneEl.is("entered")) return;

this.el.sceneEl.systems["hubs-systems"].soundEffectsSystem.playSoundOneShot(SOUND_TOGGLE_MIC);
robertlong marked this conversation as resolved.
Show resolved Hide resolved
if (this.el.is("muted")) {
NAF.connection.adapter.enableMicrophone(true);
this.el.removeState("muted");
Expand Down
4 changes: 4 additions & 0 deletions src/components/tools/pen.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ AFRAME.registerComponent("pen", {
scene.addEventListener("object3dset", this.setDirty);
scene.addEventListener("object3dremove", this.setDirty);
});

this.penSystem = this.el.sceneEl.systems["pen-tools"];
this.penSystem.register(this.el);
},

play() {
Expand Down Expand Up @@ -498,5 +501,6 @@ AFRAME.registerComponent("pen", {
this.observer.disconnect();
AFRAME.scenes[0].removeEventListener("object3dset", this.setDirty);
AFRAME.scenes[0].removeEventListener("object3dremove", this.setDirty);
this.penSystem.deregister(this.el);
}
});
54 changes: 21 additions & 33 deletions src/hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ import "./components/optional-alternative-to-not-hide";
import "./components/set-max-resolution";
import "./components/avatar-audio-source";
import "./components/avatar-inspect-collider";
import { sets as userinputSets } from "./systems/userinput/sets";

import ReactDOM from "react-dom";
import React from "react";
import { Router, Route } from "react-router-dom";
import { createBrowserHistory, createMemoryHistory } from "history";
import { pushHistoryState } from "./utils/history";
import UIRoot from "./react-components/ui-root";
import { ExitedRoomScreen } from "./react-components/room/ExitedRoomScreen";
import AuthChannel from "./utils/auth-channel";
import HubChannel from "./utils/hub-channel";
import LinkChannel from "./utils/link-channel";
Expand All @@ -149,6 +149,7 @@ import "./systems/exit-on-blur";
import "./systems/auto-pixel-ratio";
import "./systems/idle-detector";
import "./systems/camera-tools";
import "./systems/pen-tools";
import "./systems/userinput/userinput";
import "./systems/userinput/userinput-debug";
import "./systems/ui-hotkeys";
Expand Down Expand Up @@ -274,33 +275,30 @@ function mountUI(props = {}) {
const scene = document.querySelector("a-scene");
const disableAutoExitOnIdle =
qsTruthy("allow_idle") || (process.env.NODE_ENV === "development" && !qs.get("idle_timeout"));
const isCursorHoldingPen =
scene &&
(scene.systems.userinput.activeSets.includes(userinputSets.rightCursorHoldingPen) ||
scene.systems.userinput.activeSets.includes(userinputSets.leftCursorHoldingPen));
const hasActiveCamera = scene && !!scene.systems["camera-tools"].getMyCamera();
const forcedVREntryType = qsVREntryType;

ReactDOM.render(
<WrappedIntlProvider>
<Router history={history}>
<Route
render={routeProps => (
<UIRoot
{...{
scene,
isBotMode,
disableAutoExitOnIdle,
forcedVREntryType,
store,
mediaSearchStore,
isCursorHoldingPen,
hasActiveCamera,
...props,
...routeProps
}}
/>
)}
render={routeProps =>
props.roomUnavailableReason ? (
<ExitedRoomScreen reason={props.roomUnavailableReason} />
) : (
<UIRoot
{...{
scene,
isBotMode,
disableAutoExitOnIdle,
forcedVREntryType,
store,
mediaSearchStore,
...props,
...routeProps
}}
/>
)
}
/>
</Router>
</WrappedIntlProvider>,
Expand Down Expand Up @@ -1008,10 +1006,7 @@ document.addEventListener("DOMContentLoaded", async () => {
enterScene: entryManager.enterScene,
exitScene: reason => {
entryManager.exitScene();

if (reason) {
remountUI({ roomUnavailableReason: reason });
}
remountUI({ roomUnavailableReason: reason || "exited" });
},
initialIsSubscribed: subscriptions.isSubscribed(),
activeTips: scene.systems.tips.activeTips
Expand All @@ -1031,13 +1026,6 @@ document.addEventListener("DOMContentLoaded", async () => {
remountUI({ roomUnavailableReason: "left" });
});

const updateCameraUI = function(e) {
if (e.detail !== "camera") return;
remountUI({});
};
scene.addEventListener("stateadded", updateCameraUI);
scene.addEventListener("stateremoved", updateCameraUI);

scene.addEventListener("hub_closed", () => {
scene.exitVR();
entryManager.exitScene("closed");
Expand Down
1 change: 1 addition & 0 deletions src/react-components/icons/Microphone.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/react-components/icons/MicrophoneMuted.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 12 additions & 4 deletions src/react-components/popover/ButtonGridPopover.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import classNames from "classnames";
import { ToolbarButton } from "../input/ToolbarButton";
import styles from "./ButtonGridPopover.scss";

export function ButtonGridPopover({ fullscreen, items, onSelect }) {
export function ButtonGridPopover({ fullscreen, items, closePopover }) {
return (
<div className={classNames(styles.buttonGridPopover, { [styles.fullscreen]: fullscreen })}>
{items.map(item => {
Expand All @@ -14,8 +14,15 @@ export function ButtonGridPopover({ fullscreen, items, onSelect }) {
key={item.id}
icon={<Icon />}
preset={item.color}
onClick={() => onSelect(item)}
onClick={() => {
if (item.onSelect) {
item.onSelect(item);
}

closePopover();
}}
label={item.label}
selected={item.selected}
/>
);
})}
Expand All @@ -30,8 +37,9 @@ ButtonGridPopover.propTypes = {
id: PropTypes.string.isRequired,
icon: PropTypes.elementType.isRequired,
color: PropTypes.string,
name: PropTypes.string.isRequired
name: PropTypes.string.isRequired,
onSelect: PropTypes.func
})
).isRequired,
onSelect: PropTypes.func.isRequired
closePopover: PropTypes.func.isRequired
};
75 changes: 75 additions & 0 deletions src/react-components/room/ExitedRoomScreen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from "react";
import PropTypes from "prop-types";
import IfFeature from "../if-feature";
import { getMessages } from "../../utils/i18n";
import configs from "../../utils/configs";
import { FormattedMessage } from "react-intl";

export function ExitedRoomScreen({ reason }) {
let subtitle = null;
if (reason === "closed") {
// TODO i18n, due to links and markup
subtitle = (
<div>
Sorry, this room is no longer available.
<p />
<IfFeature name="show_terms">
A room may be closed by the room owner, or if we receive reports that it violates our{" "}
<a
target="_blank"
rel="noreferrer noopener"
href={configs.link("terms_of_use", "https://github.com/mozilla/hubs/blob/master/TERMS.md")}
>
Terms of Use
</a>
.<br />
</IfFeature>
If you have questions, contact us at{" "}
<a href={`mailto:${getMessages()["contact-email"]}`}>
<FormattedMessage id="contact-email" />
</a>
.<p />
<IfFeature name="show_source_link">
If you&apos;d like to run your own server, Hubs&apos;s source code is available on{" "}
<a href="https://github.com/mozilla/hubs">GitHub</a>
.
</IfFeature>
</div>
);
} else {
const tcpUrl = new URL(document.location.toString());
const tcpParams = new URLSearchParams(tcpUrl.search);
tcpParams.set("force_tcp", true);
tcpUrl.search = tcpParams.toString();

const exitSubtitleId = `exit.subtitle.${reason}`;
subtitle = (
<div>
<FormattedMessage id={exitSubtitleId} />
<p />
{reason === "connect_error" && (
<div>
You can try <a href={tcpUrl.toString()}>connecting via TCP</a>, which may work better on some networks.
</div>
)}
{!["left", "disconnected", "scene_error"].includes(reason) && (
<div>
You can also <a href="/">create a new room</a>
.
</div>
)}
</div>
);
}

return (
<div className="exited-panel">
<img className="exited-panel__logo" src={configs.image("logo")} />
<div className="exited-panel__subtitle">{subtitle}</div>
</div>
);
}

ExitedRoomScreen.propTypes = {
reason: PropTypes.string
};
4 changes: 2 additions & 2 deletions src/react-components/room/MicSetupModalContainer.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from "react";
import PropTypes from "prop-types";
import { MicSetupModal } from "./MicSetupModal";
import { useMicrophoneVolume } from "./useMicrophoneVolume";
import { useMicrophone } from "./useMicrophone";
import { useSound } from "./useSound";
import webmSrc from "../../assets/sfx/tone.webm";
import mp3Src from "../../assets/sfx/tone.mp3";
import oggSrc from "../../assets/sfx/tone.ogg";
import wavSrc from "../../assets/sfx/tone.wav";

export function MicSetupModalContainer({ scene, ...rest }) {
const volume = useMicrophoneVolume(scene);
const { volume } = useMicrophone(scene);
const [soundPlaying, playSound] = useSound({ webmSrc, mp3Src, oggSrc, wavSrc });
return <MicSetupModal micLevel={volume} soundPlaying={soundPlaying} onPlaySound={playSound} {...rest} />;
}
Expand Down
6 changes: 3 additions & 3 deletions src/react-components/room/PeopleSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ function getVoiceLabel(micPresence) {

function getVoiceIconComponent(micPresence) {
if (micPresence) {
if (micPresence.talking) {
return VolumeHighIcon;
} else if (micPresence.muted) {
if (micPresence.muted) {
return VolumeMutedIcon;
} else if (micPresence.talking) {
return VolumeHighIcon;
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/react-components/room/PlacePopover.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import { Popover } from "../popover/Popover";
import { ToolbarButton } from "../input/ToolbarButton";
import { ReactComponent as ObjectIcon } from "../icons/Object.svg";

export function PlacePopoverButton({ items, onSelect }) {
export function PlacePopoverButton({ items }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mentioned this before but at some point I hope we find a clearer word for this menu than "Place". I suspect "Place" will give people the wrong impression of it being about "this place we're in" (aka the scene), not "place a pen in your hand or an object in the scene". Some alternatives to consider:

  • Add
  • Create
  • Load
  • New
  • Spawn
  • Generate

Similarly, I think "Share" is overloaded in the mobile world and we might want to consider something like, "Broadcast" or "Stream"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll bring this up with DPX. Thanks for flagging this 👍

const filteredItems = items.filter(item => !!item);

// The button is removed if you can't place anything.
if (items.length === 0) {
return undefined;
if (filteredItems.length === 0) {
return null;
}

return (
<Popover
title="Place"
content={props => <ButtonGridPopover items={items} onSelect={onSelect} {...props} />}
content={props => <ButtonGridPopover items={filteredItems} {...props} />}
placement="top"
offsetDistance={28}
initiallyVisible
>
{({ togglePopover, popoverVisible, triggerRef }) => (
<ToolbarButton
Expand All @@ -34,6 +35,5 @@ export function PlacePopoverButton({ items, onSelect }) {
}

PlacePopoverButton.propTypes = {
items: ButtonGridPopover.propTypes.items,
onSelect: PropTypes.func
items: PropTypes.array.isRequired
};
4 changes: 1 addition & 3 deletions src/react-components/room/PlacePopover.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ const items = [
{ id: "upload", icon: UploadIcon, color: "green", label: "Upload" }
];

export const Base = () => (
<RoomLayout toolbarCenter={<PlacePopoverButton items={items} onSelect={item => console.log(item)} />} />
);
export const Base = () => <RoomLayout toolbarCenter={<PlacePopoverButton items={items} />} />;

Base.parameters = {
layout: "fullscreen"
Expand Down
Loading