Compare commits

...

10 Commits

Author SHA1 Message Date
176774295e Update snapshots 2025-07-03 04:49:47 +00:00
7e64054a8c Tiny style tweaks, set refresh icon to exclamation mark 2025-07-03 00:34:17 -04:00
1b02104614 fixup! Slot in UndoRedoButtons 2025-07-03 00:34:17 -04:00
66d0556389 Add keybindings to visual list 2025-07-03 00:34:17 -04:00
d5d8322ae6 Slot in UndoRedoButtons 2025-07-03 00:34:17 -04:00
938eb749ad Add icon for undo 2025-07-03 00:34:17 -04:00
6d4127f0ef Tiny CSS fix 2025-07-03 00:33:31 -04:00
d592794ce3 Rework top bar to not include toolbar
Closes #7679 by creating a definite top bar, and moving the toolbar
below it. Still side-steps the E2E test issue by allowing the modeling
scene extend under this top bar. I will finally address this in the next
step, which is bringing back a proper sidebar that doesn't overlay the
modeling scene.

I have a fast-follow PR coming that adds visual undo and redo buttons to
this top bar, but I wanted to keep them separate.
2025-07-03 00:18:49 -04:00
2a6904d37c Fix conflict 2025-07-03 00:18:37 -04:00
de33a3a854 Rework top bar to not include toolbar
Closes #7679 by creating a definite top bar, and moving the toolbar
below it. Still side-steps the E2E test issue by allowing the modeling
scene extend under this top bar. I will finally address this in the next
step, which is bringing back a proper sidebar that doesn't overlay the
modeling scene.

I have a fast-follow PR coming that adds visual undo and redo buttons to
this top bar, but I wanted to keep them separate.
2025-07-03 00:05:26 -04:00
38 changed files with 146 additions and 65 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -65,6 +65,8 @@ import { useModelingContext } from '@src/hooks/useModelingContext'
import { xStateValueToString } from '@src/lib/xStateValueToString'
import { getSelectionTypeDisplayText } from '@src/lib/selections'
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
import { UndoRedoButtons } from '@src/components/UndoRedoButtons'
import { Toolbar } from '@src/Toolbar'
// CYCLIC REF
sceneInfra.camControls.engineStreamActor = engineStreamActor
@ -246,15 +248,24 @@ export function App() {
return (
<div className="h-screen flex flex-col overflow-hidden select-none">
<div className="relative flex flex-1 flex-col">
<AppHeader
className="transition-opacity transition-duration-75"
project={{ project, file }}
enableMenu={true}
nativeFileMenuCreated={nativeFileMenuCreated}
>
<CommandBarOpenButton />
<ShareButton />
</AppHeader>
<div className="relative flex items-center flex-col">
<AppHeader
className="transition-opacity transition-duration-75"
project={{ project, file }}
enableMenu={true}
nativeFileMenuCreated={nativeFileMenuCreated}
projectMenuChildren={
<UndoRedoButtons
editorManager={editorManager}
className="flex items-center px-2 border-x border-chalkboard-30 dark:border-chalkboard-80"
/>
}
>
<CommandBarOpenButton />
<ShareButton />
</AppHeader>
<Toolbar />
</div>
<ModalContainer />
<ModelingSidebar />
<EngineStream pool={pool} authToken={authToken} />

View File

@ -203,7 +203,7 @@ export function Toolbar({
<menu
data-current-mode={currentMode}
data-onboarding-id="toolbar"
className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
className="z-[19] max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
>
<ul
{...props}

View File

@ -3,7 +3,6 @@
in Tailwind, such as complex grid layouts.
*/
.header {
grid-template-columns: 1fr auto 1fr;
user-select: none;
-webkit-user-select: none;
}

View File

@ -1,30 +1,30 @@
import { Toolbar } from '@src/Toolbar'
import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu'
import UserSidebarMenu from '@src/components/UserSidebarMenu'
import { isDesktop } from '@src/lib/isDesktop'
import { type IndexLoaderData } from '@src/lib/types'
import type { IndexLoaderData } from '@src/lib/types'
import { useUser } from '@src/lib/singletons'
import styles from './AppHeader.module.css'
import type { ReactNode } from 'react'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
project?: Omit<IndexLoaderData, 'code'>
className?: string
enableMenu?: boolean
style?: React.CSSProperties
nativeFileMenuCreated: boolean
projectMenuChildren?: ReactNode
}
export const AppHeader = ({
showToolbar = true,
project,
children,
className = '',
style,
enableMenu = false,
nativeFileMenuCreated,
projectMenuChildren,
}: AppHeaderProps) => {
const user = useUser()
@ -32,14 +32,9 @@ export const AppHeader = ({
<header
id="app-header"
data-testid="app-header"
className={
'w-full grid ' +
styles.header +
` ${
isDesktop() ? styles.desktopApp + ' ' : ''
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
className
}
className={`w-full flex ${styles.header || ''} ${
isDesktop() ? styles.desktopApp : ''
} overlaid-panes sticky top-0 z-20 px-2 justify-between ${className || ''} bg-chalkboard-10 dark:bg-chalkboard-90 border-b border-chalkboard-30 dark:border-chalkboard-70`}
data-native-file-menu={nativeFileMenuCreated}
style={style}
>
@ -47,13 +42,9 @@ export const AppHeader = ({
enableMenu={enableMenu}
project={project?.project}
file={project?.file}
/>
{/* Toolbar if the context deems it */}
<div className="flex flex-col items-center gap-2">
<div className="flex-grow flex justify-center max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
{showToolbar && <Toolbar />}
</div>
</div>
>
{projectMenuChildren}
</ProjectSidebarMenu>
<div className="flex items-center gap-2 py-1 ml-auto">
{/* If there are children, show them, otherwise show User menu */}
{children || <CommandBarOpenButton />}

View File

@ -10,13 +10,13 @@ export function CommandBarOpenButton() {
return (
<button
type="button"
className="flex gap-1 items-center py-0 px-0.5 m-0 text-primary dark:text-inherit bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 border border-solid border-primary/50 hover:border-primary active:border-primary"
className="flex gap-1 items-center py-0 pl-0.5 pr-1 sm:pr-0.5 m-0 text-primary dark:text-inherit bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 border border-solid border-primary/50 hover:border-primary active:border-primary"
onClick={() => commandBarActor.send({ type: 'Open' })}
data-testid="command-bar-open-button"
>
<CustomIcon name="command" className="w-5 h-5" />
<span>Commands</span>
<kbd className="dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
<kbd className="hidden sm:block dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
</kbd>
</button>

View File

@ -76,6 +76,21 @@ const CustomIconMap = {
/>
</svg>
),
arrowRotateLeft: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="arrow rotate left"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.83087 7.59684L4.83087 8.09684L5.33087 8.09684L9.5378 8.09684L9.5378 7.09684L6.56183 7.09684C7.02568 6.54751 7.61622 6.11526 8.28567 5.83941C9.11759 5.49662 10.0332 5.41109 10.9142 5.59387C11.7952 5.77666 12.6012 6.21935 13.2281 6.8648C13.855 7.51025 14.274 8.32881 14.431 9.21478C14.588 10.1008 14.4758 11.0134 14.1089 11.835C13.742 12.6566 13.1373 13.3493 12.3727 13.8237C11.6082 14.2981 10.719 14.5325 9.8199 14.4964C8.92084 14.4604 8.05323 14.1557 7.32909 13.6216L6.73554 14.4264C7.6206 15.0792 8.68102 15.4516 9.77986 15.4956C10.8787 15.5397 11.9655 15.2533 12.9 14.6734C13.8344 14.0936 14.5736 13.2469 15.022 12.2428C15.4705 11.2386 15.6076 10.1231 15.4157 9.04027C15.2238 7.95742 14.7116 6.95696 13.9454 6.16808C13.1792 5.3792 12.1941 4.83812 11.1173 4.61472C10.0405 4.39132 8.92149 4.49586 7.9047 4.91483C7.10231 5.24545 6.39268 5.7599 5.83087 6.41286L5.83087 3.38998L4.83087 3.38998L4.83087 7.59684Z"
fill="currentColor"
/>
</svg>
),
arrowRotateRight: (
<svg
viewBox="0 0 20 20"

View File

@ -131,7 +131,7 @@ export function ModelingSidebar() {
id: 'refresh',
title: 'Refresh app',
sidebarName: 'Refresh app',
icon: 'arrowRotateRight',
icon: 'exclamationMark',
keybinding: 'Mod + R',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
action: async () => {

View File

@ -17,40 +17,39 @@ import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop'
import { PATHS } from '@src/lib/paths'
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { type IndexLoaderData } from '@src/lib/types'
import type { IndexLoaderData } from '@src/lib/types'
import { commandBarActor } from '@src/lib/singletons'
interface ProjectSidebarMenuProps extends React.PropsWithChildren {
enableMenu?: boolean
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}
const ProjectSidebarMenu = ({
project,
file,
enableMenu = false,
}: {
enableMenu?: boolean
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}) => {
children,
}: ProjectSidebarMenuProps) => {
// 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 (
<div
className={
'!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2 ' +
trafficLightsOffset
}
>
<div className={'!no-underline flex gap-2 ' + trafficLightsOffset}>
<AppLogoLink project={project} file={file} />
{enableMenu ? (
<ProjectMenuPopover project={project} file={file} />
) : (
<span
className="hidden select-none cursor-default text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
className="hidden self-center px-2 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>
)}
{children}
</div>
)
}
@ -64,7 +63,7 @@ function AppLogoLink({
}) {
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"
"relative h-full grid flex-none place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-1 before:z-[-1] before:bg-primary before:rounded-b-sm"
const logoClassName = 'w-auto h-4 text-chalkboard-10'
return isDesktop() ? (
@ -238,12 +237,23 @@ function ProjectMenuPopover({
return (
<Popover className="relative">
<Popover.Button
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-appForeground dark:hover:bg-chalkboard-90"
className="gap-1 rounded-sm mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center focus-visible:outline-appForeground dark:hover:bg-chalkboard-90"
data-testid="project-sidebar-toggle"
>
<div className="flex flex-col items-start py-0.5">
<div className="flex items-baseline py-0.5 text-sm gap-1">
{isDesktop() && project?.name && (
<>
<span
className="hidden whitespace-nowrap md:block"
data-testid="app-header-project-name"
>
{project.name}
</span>
<span className="hidden md:block">/</span>
</>
)}
<span
className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap"
data-testid="app-header-file-name"
>
{isDesktop() && file?.name
@ -252,14 +262,6 @@ function ProjectMenuPopover({
)
: APP_NAME}
</span>
{isDesktop() && project?.name && (
<span
className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block"
data-testid="app-header-project-name"
>
{project.name}
</span>
)}
</div>
<CustomIcon
name="caretDown"

View File

@ -83,7 +83,7 @@ export const ShareButton = memo(function ShareButton() {
billingContext.tier === undefined
return (
<Popover className="relative flex">
<Popover className="relative hidden sm:flex">
<Popover.Button
as="div"
className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"

View File

@ -0,0 +1,50 @@
import type EditorManager from '@src/editor/manager'
import usePlatform from '@src/hooks/usePlatform'
import type { HTMLProps } from 'react'
import { CustomIcon } from '@src/components/CustomIcon'
import Tooltip from '@src/components/Tooltip'
import { hotkeyDisplay } from '@src/lib/hotkeyWrapper'
export function UndoRedoButtons({
editorManager,
...props
}: HTMLProps<HTMLDivElement> & { editorManager: EditorManager }) {
const platform = usePlatform()
return (
<div {...props}>
<button
type="button"
onClick={() => editorManager.undo()}
className="p-0 m-0 border-transparent dark:border-transparent focus-visible:border-chalkboard-100"
>
<CustomIcon name="arrowRotateLeft" className="w-6 h-6" />
<Tooltip
position="bottom"
contentClassName="text-sm max-w-none flex items-center gap-4"
>
<span>Undo</span>
<kbd className="hotkey capitalize">
{hotkeyDisplay('mod+z', platform)}
</kbd>
</Tooltip>
</button>
<button
type="button"
onClick={() => editorManager.redo()}
className="p-0 m-0 border-transparent dark:border-transparent focus-visible:border-chalkboard-100"
>
<CustomIcon name="arrowRotateRight" className="w-6 h-6" />
<Tooltip
position="bottom"
contentClassName="text-sm max-w-none flex items-center gap-4"
>
<span>Redo</span>
<kbd className="hotkey capitalize">
{hotkeyDisplay('mod+shift+z', platform)}
</kbd>
</Tooltip>
</button>
</div>
)
}

View File

@ -178,9 +178,9 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
}
return (
<Popover className="relative">
<Popover className="relative grid">
<Popover.Button
className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"
className="m-0 relative group border-0 w-fit min-w-max p-0 rounded-l-full rounded-r focus-visible:outline-appForeground"
data-testid="user-sidebar-toggle"
>
<div className="flex items-center">

View File

@ -185,6 +185,22 @@ export const interactionMap: Record<
'Available when a file or folder is selected in the file tree.',
},
],
Miscellaneous: [
{
name: 'undo',
sequence: `${PRIMARY}+Z`,
title: 'Undo',
description:
'Available while modeling and writing code. Currently only steps back in modeling history, or code history.',
},
{
name: 'redo',
sequence: `${PRIMARY}+Shift+Z`,
title: 'Redo',
description:
'Available while modeling and writing code. Currently only steps forward in modeling history, or code history.',
},
],
}
/**

View File

@ -221,10 +221,7 @@ const Home = () => {
return (
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
<AppHeader
nativeFileMenuCreated={nativeFileMenuCreated}
showToolbar={false}
/>
<AppHeader nativeFileMenuCreated={nativeFileMenuCreated} />
<div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
<HomeHeader
setQuery={setQuery}