import { Popover, Transition } from '@headlessui/react' import { useSelector } from '@xstate/react' import { Fragment, useContext, useMemo } from 'react' import { Link, useLocation, useNavigate } from 'react-router-dom' import type { SnapshotFrom } from 'xstate' import type { ActionButtonProps } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton' import { CustomIcon } from '@src/components/CustomIcon' import { Logo } from '@src/components/Logo' import { useLspContext } from '@src/components/LspProvider' import { MachineManagerContext } from '@src/components/MachineManagerProvider' import Tooltip from '@src/components/Tooltip' import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath' import usePlatform from '@src/hooks/usePlatform' import { APP_NAME } from '@src/lib/constants' import { isDesktop } from '@src/lib/isDesktop' import { copyFileShareLink } from '@src/lib/links' import { PATHS } from '@src/lib/paths' import { codeManager, engineCommandManager, kclManager, } from '@src/lib/singletons' import { type IndexLoaderData } from '@src/lib/types' import { useToken } from '@src/machines/appMachine' import { commandBarActor } from '@src/machines/commandBarMachine' const ProjectSidebarMenu = ({ project, file, enableMenu = false, }: { enableMenu?: boolean project?: IndexLoaderData['project'] file?: IndexLoaderData['file'] }) => { // Make room for traffic lights on desktop left side. // TODO: make sure this doesn't look like shit on Linux or Windows const trafficLightsOffset = isDesktop() && window.electron.os.isMac ? 'ml-20' : '' return (
{enableMenu ? ( ) : ( {project?.name ? project.name : APP_NAME} )}
) } function AppLogoLink({ project, file, }: { project?: IndexLoaderData['project'] file?: IndexLoaderData['file'] }) { const { onProjectClose } = useLspContext() const wrapperClassName = "relative h-full grid place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-2.5 before:z-[-1] before:bg-primary before:rounded-b-sm" const logoClassName = 'w-auto h-4 text-chalkboard-10' return isDesktop() ? ( { onProjectClose(file || null, project?.path || null, false) kclManager.switchedFiles = true }} to={PATHS.HOME} className={wrapperClassName + ' hover:before:brightness-110'} > {APP_NAME} ) : (
{APP_NAME}
) } const commandsSelector = (state: SnapshotFrom) => state.context.commands function ProjectMenuPopover({ project, file, }: { project?: IndexLoaderData['project'] file?: IndexLoaderData['file'] }) { const platform = usePlatform() const location = useLocation() const navigate = useNavigate() const filePath = useAbsoluteFilePath() const token = useToken() const machineManager = useContext(MachineManagerContext) const commands = useSelector(commandBarActor, commandsSelector) const { onProjectClose } = useLspContext() const insertCommandInfo = { name: 'Insert', groupId: 'code' } const exportCommandInfo = { name: 'Export', groupId: 'modeling' } const makeCommandInfo = { name: 'Make', groupId: 'modeling' } const shareCommandInfo = { name: 'share-file-link', groupId: 'code' } const findCommand = (obj: { name: string; groupId: string }) => Boolean( commands.find((c) => c.name === obj.name && c.groupId === obj.groupId) ) const machineCount = machineManager.machines.length // We filter this memoized list so that no orphan "break" elements are rendered. const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>( () => [ { id: 'settings', Element: 'button', children: ( <> Project settings {`${platform === 'macos' ? '⌘' : 'Ctrl'}${ isDesktop() ? '' : '⬆' },`} ), onClick: () => { const targetPath = location.pathname.includes(PATHS.FILE) ? filePath + PATHS.SETTINGS_PROJECT : PATHS.HOME + PATHS.SETTINGS_PROJECT navigate(targetPath) }, }, 'break', { id: 'insert', Element: 'button', children: ( <> Insert from project file {!findCommand(insertCommandInfo) && ( Awaiting engine connection )} ), disabled: !findCommand(insertCommandInfo), onClick: () => commandBarActor.send({ type: 'Find and select command', data: insertCommandInfo, }), }, { id: 'export', Element: 'button', children: ( <> Export current part {!findCommand(exportCommandInfo) && ( Awaiting engine connection )} ), disabled: !findCommand(exportCommandInfo), onClick: () => commandBarActor.send({ type: 'Find and select command', data: exportCommandInfo, }), }, { id: 'make', Element: 'button', className: !isDesktop() ? 'hidden' : '', children: ( <> Make current part {!findCommand(makeCommandInfo) && ( Awaiting engine connection )} ), disabled: !findCommand(makeCommandInfo) || machineCount === 0, onClick: () => { commandBarActor.send({ type: 'Find and select command', data: makeCommandInfo, }) }, }, { id: 'share-link', Element: 'button', children: 'Share part via Zoo link', disabled: !findCommand(shareCommandInfo), onClick: async () => { await copyFileShareLink({ token: token ?? '', code: codeManager.code, name: project?.name || '', }) }, }, 'break', { id: 'go-home', Element: 'button', children: 'Go to Home', className: !isDesktop() ? 'hidden' : '', onClick: () => { onProjectClose(file || null, project?.path || null, true) kclManager.switchedFiles = true }, }, ].filter( (props) => props === 'break' || (typeof props !== 'string' && !props.className?.includes('hidden')) ) as (ActionButtonProps | 'break')[], [ platform, findCommand, commandBarActor.send, engineCommandManager, onProjectClose, isDesktop, ] ) return (
{isDesktop() && file?.name ? file.name.slice( file.name.lastIndexOf(window.electron.path.sep) + 1 ) : APP_NAME} {isDesktop() && project?.name && ( {project.name} )}
{({ close }) => (
    {projectMenuItems.map((props, index) => { if (props === 'break') { return index !== projectMenuItems.length - 1 ? (

  • ) : null } const { id, className, children, ...rest } = props return (
  • { close() }} > {children}
  • ) })}
)}
) } export default ProjectSidebarMenu