diff --git a/fission/bun.lock b/fission/bun.lock index 41f3330c0c..6aaaff01a0 100644 --- a/fission/bun.lock +++ b/fission/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "synthesis-fission", diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index bad1eb5d69..1830f272e0 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -3,7 +3,7 @@ import { SnackbarProvider } from "notistack" import Slide from "@mui/material/Slide" import { useCallback, useEffect, useRef, useState } from "react" import { globalAddToast } from "@/components/GlobalUIControls.ts" -import MainHUD from "@/components/MainHUD" +import MainHUD, { EVENT_KEY_OPEN_MAIN_HUD } from "@/components/MainHUD" import MultiplayerHUD from "@/components/MultiplayerHUD.tsx" import Scene from "@/components/Scene.tsx" import MultiplayerStartModal from "@/modals/MultiplayerStartModal.tsx" @@ -40,6 +40,8 @@ function Synthesis() { World.updateWorld() } + document.dispatchEvent(new Event(EVENT_KEY_OPEN_MAIN_HUD)) + mainLoop() } useEffect(() => { diff --git a/fission/src/ui/components/HelpPopover.tsx b/fission/src/ui/components/HelpPopover.tsx new file mode 100644 index 0000000000..4ac64db7c6 --- /dev/null +++ b/fission/src/ui/components/HelpPopover.tsx @@ -0,0 +1,71 @@ +import { Button, List, ListItemButton, ListItemIcon, ListItemText, Popover } from "@mui/material" +import { IoHelpCircle } from "react-icons/io5" +import { useCallback, useState } from "react" +import { type HelpOption, HelpOptionType } from "@/util/HelpOption" +import { FaDiscord, FaYoutube } from "react-icons/fa6" +import { MdArticle } from "react-icons/md" + +function helpOptionIcon(type: HelpOptionType) { + switch (type) { + case HelpOptionType.YouTube: + return + case HelpOptionType.Codelab: + return + case HelpOptionType.Discord: + return + case HelpOptionType.Website: + return + default: + return + } +} + +const HelpPopover: React.FC<{ id: string; helpOptions?: HelpOption[] }> = ({ id, helpOptions }) => { + const [helpPopoverAnchor, setHelpPopoverAnchor] = useState(null) + + const handleHelpButton = useCallback((event: React.MouseEvent) => { + setHelpPopoverAnchor(event.currentTarget) + }, []) + + const handleCloseHelpPopover = useCallback(() => { + setHelpPopoverAnchor(null) + }, []) + + const helpPopoverOpen = Boolean(helpPopoverAnchor) + const helpPopoverId = helpPopoverOpen ? id : undefined + + return ( + helpOptions && ( + <> + + + + {helpOptions.map(x => ( + window.open(x.link, "_blank", "noopener, noreferrer")}> + {helpOptionIcon(x.type)} + + + ))} + + + + ) + ) +} + +export { HelpPopover } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index c17da72259..d92baddb30 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -1,4 +1,4 @@ -import { Box, ButtonGroup, ButtonProps, Stack } from "@mui/material" +import { Box, ButtonGroup, type ButtonProps, Stack } from "@mui/material" import { motion } from "framer-motion" import type React from "react" import { useEffect, useState } from "react" @@ -22,6 +22,23 @@ import { setAddToast, setOpenModal, setOpenPanel } from "./GlobalUIControls" import { Button, IconButton, SynthesisIcons } from "./StyledComponents" import { TouchControlsEvent, TouchControlsEventKeys } from "./TouchControls" import UserIcon from "./UserIcon" +import { HelpPopover } from "./HelpPopover" +import { + HELP_OPTION_ALL_TUTORIALS, + HELP_OPTION_DISCORD, + HELP_OPTION_EXPORT_CODELAB, + HELP_OPTION_SPAWN_ASSET_YOUTUBE, + type HelpOption, +} from "@/util/HelpOption" + +const HELP_OPTIONS: HelpOption[] = [ + HELP_OPTION_SPAWN_ASSET_YOUTUBE, + HELP_OPTION_EXPORT_CODELAB, + HELP_OPTION_ALL_TUTORIALS, + HELP_OPTION_DISCORD, +] + +export const EVENT_KEY_OPEN_MAIN_HUD = "openMainHud" const MainHUDButton: React.FC = ({ startIcon, endIcon, children, ...props }) => { return ( @@ -61,6 +78,17 @@ const MainHUD: React.FC = () => { const [userInfo, setUserInfo] = useState(APS.userInfo) const [matchModeRunning, setMatchModeRunning] = useState(MatchMode.getInstance().isMatchEnabled()) + useEffect(() => { + const handler = (event: Event) => { + if (event.type === EVENT_KEY_OPEN_MAIN_HUD) setIsOpen(true) + } + + document.addEventListener(EVENT_KEY_OPEN_MAIN_HUD, handler) + return () => { + document.removeEventListener(EVENT_KEY_OPEN_MAIN_HUD, handler) + } + }, []) + useEffect(() => { document.addEventListener(APS_USER_INFO_UPDATE_EVENT, () => { setUserInfo(APS.userInfo) @@ -267,6 +295,7 @@ const MainHUD: React.FC = () => { Abort Match Mode )} + ) diff --git a/fission/src/ui/components/Modal.tsx b/fission/src/ui/components/Modal.tsx index 33e961d601..258c32f220 100644 --- a/fission/src/ui/components/Modal.tsx +++ b/fission/src/ui/components/Modal.tsx @@ -1,8 +1,9 @@ -import { Card, CardActions, CardContent, CardHeader, Modal as MUIModal } from "@mui/material" -import React, { type ReactElement } from "react" +import { Box, Card, CardActions, CardContent, CardHeader, Modal as MUIModal, Typography } from "@mui/material" +import React, { useMemo, type ReactElement } from "react" import type { Modal as ModalType, Panel as PanelType } from "../helpers/UIProviderHelpers" import { CloseType, useUIContext } from "../helpers/UIProviderHelpers" import { Button } from "./StyledComponents" +import { HelpPopover } from "./HelpPopover" export type ModalImplProps = Partial<{ modal: ModalType @@ -22,6 +23,15 @@ export const Modal = ({ children, modal, parent }: ModalElementProps const props = modal.props + const header = useMemo(() => { + return ( + + {props.title} + + + ) + }, [props.title]) + return ( ({ children, modal, parent }: ModalElementProps > {props.title && ( = Partial<{ @@ -59,6 +60,23 @@ export const Panel = ({ children, panel, parent }: PanelElementProps const props = panel.props const nodeRef = useRef(null) + const header = useMemo(() => { + return ( + + {props.title} + + + ) + }, [props.title]) + // FIXME: sliders show up as so want to cancel drag on those // however still can drag on dropdown but menu elements are left behind return ( @@ -84,7 +102,7 @@ export const Panel = ({ children, panel, parent }: PanelElementProps > {props.title && ( { disableAccept?: boolean cancelText?: string acceptText?: string + helpOptions?: HelpOption[] custom: P } diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index bdcc3795d4..a7a9ac8e02 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -35,6 +35,7 @@ import { Tab, Tabs } from "@mui/material" import { SoundPlayer } from "@/systems/sound/SoundPlayer" import CommandRegistry, { type CommandDefinition, type CommandProvider } from "@/ui/components/CommandRegistry" import { globalOpenPanel } from "@/ui/components/GlobalUIControls" +import { HELP_OPTION_CONFIGURE_YOUTUBE } from "@/util/HelpOption" // Register command: Configure Assets (module-scope side effect) CommandRegistry.get().registerCommands([ @@ -314,7 +315,12 @@ const ConfigurePanel: React.FC> configureScreen( panel!, - { title: "Configure Assets", acceptText: "Save", cancelText: "Cancel" }, + { + title: "Configure Assets", + acceptText: "Save", + cancelText: "Cancel", + helpOptions: [HELP_OPTION_CONFIGURE_YOUTUBE], + }, { onBeforeAccept, onCancel } ) }, [selectedAssembly, pendingDeletes]) diff --git a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx index b9739dfa15..157e1affba 100644 --- a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx +++ b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx @@ -21,6 +21,7 @@ import { useUIContext } from "@/ui/helpers/UIProviderHelpers" import NewInputSchemeModal from "@/ui/modals/configuring/inputs/NewInputSchemeModal" import ConfigurePanel from "../assembly-config/ConfigurePanel" import InputSchemeSelection from "./InputSchemeSelection" +import { HELP_OPTION_SPAWN_ASSET_YOUTUBE } from "@/util/HelpOption" const InitialConfigPanel: React.FC> = ({ panel }) => { // TODO: can we pass these as custom props? @@ -76,7 +77,12 @@ const InitialConfigPanel: React.FC> = ({ panel }) => configureScreen( panel!, - { title: "Assembly Setup", acceptText: "Finish", cancelText: "Remove" }, + { + title: "Assembly Setup", + acceptText: "Finish", + cancelText: "Remove", + helpOptions: [HELP_OPTION_SPAWN_ASSET_YOUTUBE], + }, { onBeforeAccept: () => { closeFinish() diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index 436ecbe7f6..54144ff180 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -45,6 +45,7 @@ import { import InitialConfigPanel from "../configuring/initial-config/InitialConfigPanel" import CommandRegistry from "@/ui/components/CommandRegistry" import type { CustomOrbitControls } from "@/systems/scene/CameraControls" +import { HELP_OPTION_SPAWN_ASSET_YOUTUBE } from "@/util/HelpOption" // Register commands: Open import panel scoped to robots/fields (module-scope side effect) CommandRegistry.get().registerCommands([ @@ -192,7 +193,16 @@ const ImportMirabufPanel: React.FC(undefined) useEffect(() => { - configureScreen(panel!, { title: "Spawn Asset", hideAccept: true, cancelText: "Back" }, {}) + configureScreen( + panel!, + { + title: "Spawn Asset", + hideAccept: true, + cancelText: "Back", + helpOptions: [HELP_OPTION_SPAWN_ASSET_YOUTUBE], + }, + {} + ) }, []) useEffect(() => { diff --git a/fission/src/util/HelpOption.ts b/fission/src/util/HelpOption.ts new file mode 100644 index 0000000000..008752ffc9 --- /dev/null +++ b/fission/src/util/HelpOption.ts @@ -0,0 +1,42 @@ +export enum HelpOptionType { + YouTube = "YouTube", + Codelab = "Codelab", + Website = "Website", + Discord = "Discord", +} + +export type HelpOption = { + text: string + link: string + type: HelpOptionType +} + +export const HELP_OPTION_SPAWN_ASSET_YOUTUBE: HelpOption = { + text: "How to Spawn a Robot/Field", + link: "https://youtu.be/u608dgHAc2s?si=25tbYWZpHNxGdVzU", + type: HelpOptionType.YouTube, +} + +export const HELP_OPTION_CONFIGURE_YOUTUBE: HelpOption = { + text: "How to Configure a Robot/Field", + link: "https://youtu.be/hHf-7Ojl-fE?si=6xpjRkiFwjrosDyd", + type: HelpOptionType.YouTube, +} + +export const HELP_OPTION_DISCORD: HelpOption = { + text: "Synthesis Community", + link: "https://discord.gg/AnGhEyAn", + type: HelpOptionType.Discord, +} + +export const HELP_OPTION_EXPORT_CODELAB: HelpOption = { + text: "Exporter Guide", + link: "https://synthesis.autodesk.com/codelab/FusionExporterCodelab/#0", + type: HelpOptionType.Codelab, +} + +export const HELP_OPTION_ALL_TUTORIALS: HelpOption = { + text: "All Tutorials", + link: "https://synthesis.autodesk.com/tutorials.html", + type: HelpOptionType.Website, +}