2023-08-31 08:17:52 -04:00
|
|
|
import { Popover, Transition } from '@headlessui/react'
|
2024-07-18 14:29:15 -04:00
|
|
|
import { ActionButton, ActionButtonProps } from './ActionButton'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { type IndexLoaderData } from 'lib/types'
|
|
|
|
import { paths } from 'lib/paths'
|
2023-08-18 10:27:01 -04:00
|
|
|
import { isTauri } from '../lib/isTauri'
|
2024-07-18 14:29:15 -04:00
|
|
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
|
|
|
import { Fragment, useMemo } from 'react'
|
2023-10-17 12:31:14 -04:00
|
|
|
import { sep } from '@tauri-apps/api/path'
|
2023-12-18 06:15:26 -05:00
|
|
|
import { Logo } from './Logo'
|
|
|
|
import { APP_NAME } from 'lib/constants'
|
2024-03-04 16:06:43 -05:00
|
|
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
2024-03-08 17:59:14 -05:00
|
|
|
import { CustomIcon } from './CustomIcon'
|
2024-03-11 17:50:31 -07:00
|
|
|
import { useLspContext } from './LspProvider'
|
2024-04-18 12:40:05 -07:00
|
|
|
import { engineCommandManager } from 'lib/singletons'
|
2024-07-18 14:29:15 -04:00
|
|
|
import usePlatform from 'hooks/usePlatform'
|
|
|
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
|
|
|
import Tooltip from './Tooltip'
|
2023-08-18 10:27:01 -04:00
|
|
|
|
|
|
|
const ProjectSidebarMenu = ({
|
|
|
|
project,
|
2023-10-16 13:28:41 -04:00
|
|
|
file,
|
2024-05-20 14:59:59 -04:00
|
|
|
enableMenu = false,
|
2023-08-18 10:27:01 -04:00
|
|
|
}: {
|
2024-05-20 14:59:59 -04:00
|
|
|
enableMenu?: boolean
|
2023-10-16 13:28:41 -04:00
|
|
|
project?: IndexLoaderData['project']
|
|
|
|
file?: IndexLoaderData['file']
|
2023-08-18 10:27:01 -04:00
|
|
|
}) => {
|
2024-03-08 17:59:14 -05:00
|
|
|
return (
|
2024-04-16 14:29:33 -04:00
|
|
|
<div className="!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2">
|
2024-05-20 14:59:59 -04:00
|
|
|
<AppLogoLink project={project} file={file} />
|
|
|
|
{enableMenu ? (
|
2024-03-08 17:59:14 -05:00
|
|
|
<ProjectMenuPopover project={project} file={file} />
|
2024-05-20 14:59:59 -04:00
|
|
|
) : (
|
|
|
|
<span
|
|
|
|
className="hidden select-none cursor-default text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
|
|
|
|
data-testid="project-name"
|
|
|
|
>
|
|
|
|
{project?.name ? project.name : APP_NAME}
|
|
|
|
</span>
|
2024-03-08 17:59:14 -05:00
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-05-20 14:59:59 -04:00
|
|
|
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 isTauri() ? (
|
|
|
|
<Link
|
|
|
|
data-testid="app-logo"
|
|
|
|
onClick={() => {
|
|
|
|
onProjectClose(file || null, project?.path || null, false)
|
|
|
|
// Clear the scene and end the session.
|
|
|
|
engineCommandManager.endSession()
|
|
|
|
}}
|
|
|
|
to={paths.HOME}
|
|
|
|
className={wrapperClassName + ' hover:before:brightness-110'}
|
|
|
|
>
|
|
|
|
<Logo className={logoClassName} />
|
|
|
|
<span className="sr-only">{APP_NAME}</span>
|
|
|
|
</Link>
|
|
|
|
) : (
|
|
|
|
<div className={wrapperClassName} data-testid="app-logo">
|
|
|
|
<Logo className={logoClassName} />
|
|
|
|
<span className="sr-only">{APP_NAME}</span>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-03-08 17:59:14 -05:00
|
|
|
function ProjectMenuPopover({
|
|
|
|
project,
|
|
|
|
file,
|
|
|
|
}: {
|
|
|
|
project?: IndexLoaderData['project']
|
|
|
|
file?: IndexLoaderData['file']
|
|
|
|
}) {
|
2024-07-18 14:29:15 -04:00
|
|
|
const platform = usePlatform()
|
|
|
|
const location = useLocation()
|
|
|
|
const navigate = useNavigate()
|
|
|
|
const filePath = useAbsoluteFilePath()
|
2024-04-04 19:15:26 -04:00
|
|
|
const { commandBarState, commandBarSend } = useCommandsContext()
|
2024-03-11 17:50:31 -07:00
|
|
|
const { onProjectClose } = useLspContext()
|
2024-07-11 18:10:47 -04:00
|
|
|
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
|
|
|
const findCommand = (obj: { name: string; groupId: string }) =>
|
2024-04-04 19:15:26 -04:00
|
|
|
Boolean(
|
|
|
|
commandBarState.context.commands.find(
|
2024-07-11 18:10:47 -04:00
|
|
|
(c) => c.name === obj.name && c.groupId === obj.groupId
|
2024-04-04 19:15:26 -04:00
|
|
|
)
|
|
|
|
)
|
2024-03-04 16:06:43 -05:00
|
|
|
|
2024-07-18 14:29:15 -04:00
|
|
|
// We filter this memoized list so that no orphan "break" elements are rendered.
|
|
|
|
const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
|
|
|
() =>
|
|
|
|
[
|
|
|
|
{
|
|
|
|
id: 'settings',
|
|
|
|
Element: 'button',
|
|
|
|
children: (
|
|
|
|
<>
|
|
|
|
<span className="flex-1">Project settings</span>
|
|
|
|
<kbd className="hotkey">{`${platform === 'macos' ? '⌘' : 'Ctrl'}${
|
|
|
|
isTauri() ? '' : '⬆'
|
|
|
|
},`}</kbd>
|
|
|
|
</>
|
|
|
|
),
|
|
|
|
onClick: () => {
|
|
|
|
const targetPath = location.pathname.includes(paths.FILE)
|
|
|
|
? filePath + paths.SETTINGS
|
|
|
|
: paths.HOME + paths.SETTINGS
|
|
|
|
navigate(targetPath + '?tab=project')
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'break',
|
|
|
|
{
|
|
|
|
id: 'export',
|
|
|
|
Element: 'button',
|
|
|
|
children: (
|
|
|
|
<>
|
|
|
|
<span>Export current part</span>
|
|
|
|
{!findCommand(exportCommandInfo) && (
|
2024-07-24 23:33:31 -04:00
|
|
|
<Tooltip
|
|
|
|
position="right"
|
|
|
|
wrapperClassName="!max-w-none min-w-fit"
|
|
|
|
>
|
2024-07-18 14:29:15 -04:00
|
|
|
Awaiting engine connection
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
),
|
|
|
|
disabled: !findCommand(exportCommandInfo),
|
|
|
|
onClick: () =>
|
|
|
|
commandBarSend({
|
|
|
|
type: 'Find and select command',
|
|
|
|
data: exportCommandInfo,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
'break',
|
|
|
|
{
|
|
|
|
id: 'go-home',
|
|
|
|
Element: 'button',
|
|
|
|
children: 'Go to Home',
|
|
|
|
className: !isTauri() ? 'hidden' : '',
|
|
|
|
onClick: () => {
|
|
|
|
onProjectClose(file || null, project?.path || null, true)
|
|
|
|
// Clear the scene and end the session.
|
|
|
|
engineCommandManager.endSession()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
].filter(
|
|
|
|
(props) =>
|
|
|
|
props === 'break' ||
|
|
|
|
(typeof props !== 'string' && !props.className?.includes('hidden'))
|
|
|
|
) as (ActionButtonProps | 'break')[],
|
|
|
|
[
|
|
|
|
platform,
|
|
|
|
findCommand,
|
|
|
|
commandBarSend,
|
|
|
|
engineCommandManager,
|
|
|
|
onProjectClose,
|
|
|
|
isTauri,
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2024-03-08 17:59:14 -05:00
|
|
|
return (
|
2023-08-18 10:27:01 -04:00
|
|
|
<Popover className="relative">
|
|
|
|
<Popover.Button
|
2024-07-18 14:29:15 -04:00
|
|
|
className="gap-1 rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary dark:hover:bg-chalkboard-90"
|
2023-08-18 10:27:01 -04:00
|
|
|
data-testid="project-sidebar-toggle"
|
|
|
|
>
|
2023-10-16 13:28:41 -04:00
|
|
|
<div className="flex flex-col items-start py-0.5">
|
2023-10-17 12:31:14 -04:00
|
|
|
<span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
|
2023-10-16 13:28:41 -04:00
|
|
|
{isTauri() && file?.name
|
2024-04-09 08:04:36 -04:00
|
|
|
? file.name.slice(file.name.lastIndexOf(sep()) + 1)
|
2023-12-18 06:15:26 -05:00
|
|
|
: APP_NAME}
|
2023-10-16 13:28:41 -04:00
|
|
|
</span>
|
|
|
|
{isTauri() && project?.name && (
|
2023-10-17 12:31:14 -04:00
|
|
|
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
|
2023-10-16 13:28:41 -04:00
|
|
|
{project.name}
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
</div>
|
2024-07-18 14:29:15 -04:00
|
|
|
<CustomIcon
|
|
|
|
name="caretDown"
|
|
|
|
className="w-4 h-4 text-chalkboard-70 dark:text-chalkboard-40 ui-open:rotate-180"
|
|
|
|
/>
|
2023-08-18 10:27:01 -04:00
|
|
|
</Popover.Button>
|
|
|
|
|
2023-08-31 08:17:52 -04:00
|
|
|
<Transition
|
|
|
|
enter="duration-100 ease-out"
|
2024-07-18 14:29:15 -04:00
|
|
|
enterFrom="opacity-0 -translate-y-2"
|
|
|
|
enterTo="opacity-100 translate-y-0"
|
2023-08-31 08:17:52 -04:00
|
|
|
as={Fragment}
|
|
|
|
>
|
2023-10-16 13:28:41 -04:00
|
|
|
<Popover.Panel
|
2024-07-18 14:29:15 -04:00
|
|
|
className={`z-10 absolute top-full left-0 mt-1 pb-1 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
|
|
|
border border-solid border-chalkboard-20 dark:border-chalkboard-90 rounded
|
|
|
|
shadow-lg`}
|
2023-10-16 13:28:41 -04:00
|
|
|
>
|
|
|
|
{({ close }) => (
|
2024-07-18 14:29:15 -04:00
|
|
|
<ul className="relative flex flex-col items-stretch content-stretch p-0.5">
|
|
|
|
{projectMenuItems.map((props, index) => {
|
|
|
|
if (props === 'break') {
|
|
|
|
return index !== projectMenuItems.length - 1 ? (
|
|
|
|
<li key={`break-${index}`} className="contents">
|
|
|
|
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />
|
|
|
|
</li>
|
|
|
|
) : null
|
|
|
|
}
|
|
|
|
|
|
|
|
const { id, className, children, ...rest } = props
|
|
|
|
return (
|
|
|
|
<li key={id} className="contents">
|
|
|
|
<ActionButton
|
|
|
|
{...rest}
|
|
|
|
className={
|
|
|
|
'relative !font-sans flex items-center gap-2 rounded-sm py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left ' +
|
|
|
|
className
|
|
|
|
}
|
|
|
|
onMouseUp={() => {
|
|
|
|
close()
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</ActionButton>
|
|
|
|
</li>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</ul>
|
2023-10-16 13:28:41 -04:00
|
|
|
)}
|
2023-08-31 08:17:52 -04:00
|
|
|
</Popover.Panel>
|
|
|
|
</Transition>
|
2023-08-18 10:27:01 -04:00
|
|
|
</Popover>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default ProjectSidebarMenu
|