Compare commits

...

7 Commits

Author SHA1 Message Date
ec4fd08a7d Update package-lock.json, unsure why 2025-07-03 09:47:26 -04:00
40388b80e7 Remove circular dependency, avoid adding 2 new ones 2025-07-03 09:47:05 -04:00
4b00d00977 Update snapshots 2025-07-03 09:47:05 -04:00
fc574fa638 Tiny CSS fix 2025-07-03 09:47:05 -04:00
0bea2bcc98 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 09:47:05 -04:00
a1059d547a Fix conflict 2025-07-03 09:47:05 -04:00
e4b40f6d36 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 09:47:05 -04:00
48 changed files with 114 additions and 78 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 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: 49 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: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 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: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 57 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: 48 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: 117 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: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 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

19
package-lock.json generated
View File

@ -26640,10 +26640,27 @@
"vscode-uri": "^3.1.0" "vscode-uri": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.14.1", "@types/node": "^24.0.7",
"ts-node": "^10.9.2" "ts-node": "^10.9.2"
} }
}, },
"packages/codemirror-lsp-client/node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
}
},
"packages/codemirror-lsp-client/node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true,
"license": "MIT"
},
"rust/kcl-language-server": { "rust/kcl-language-server": {
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",

View File

@ -10,4 +10,3 @@
5) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts 5) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts
6) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts 6) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts 7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts
8) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/setAngleLength.tsx -> src/components/SetAngleLengthModal.tsx -> src/lib/useCalculateKclExpression.ts

View File

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

View File

@ -203,7 +203,7 @@ export function Toolbar({
<menu <menu
data-current-mode={currentMode} data-current-mode={currentMode}
data-onboarding-id="toolbar" 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 <ul
{...props} {...props}

View File

@ -3,7 +3,6 @@
in Tailwind, such as complex grid layouts. in Tailwind, such as complex grid layouts.
*/ */
.header { .header {
grid-template-columns: 1fr auto 1fr;
user-select: none; user-select: none;
-webkit-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 { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu' import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu'
import UserSidebarMenu from '@src/components/UserSidebarMenu' import UserSidebarMenu from '@src/components/UserSidebarMenu'
import { isDesktop } from '@src/lib/isDesktop' 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 { useUser } from '@src/lib/singletons'
import styles from './AppHeader.module.css' import styles from './AppHeader.module.css'
import type { ReactNode } from 'react'
interface AppHeaderProps extends React.PropsWithChildren { interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
project?: Omit<IndexLoaderData, 'code'> project?: Omit<IndexLoaderData, 'code'>
className?: string className?: string
enableMenu?: boolean enableMenu?: boolean
style?: React.CSSProperties style?: React.CSSProperties
nativeFileMenuCreated: boolean nativeFileMenuCreated: boolean
projectMenuChildren?: ReactNode
} }
export const AppHeader = ({ export const AppHeader = ({
showToolbar = true,
project, project,
children, children,
className = '', className = '',
style, style,
enableMenu = false, enableMenu = false,
nativeFileMenuCreated, nativeFileMenuCreated,
projectMenuChildren,
}: AppHeaderProps) => { }: AppHeaderProps) => {
const user = useUser() const user = useUser()
@ -32,14 +32,9 @@ export const AppHeader = ({
<header <header
id="app-header" id="app-header"
data-testid="app-header" data-testid="app-header"
className={ className={`w-full flex ${styles.header || ''} ${
'w-full grid ' + isDesktop() ? styles.desktopApp : ''
styles.header + } 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`}
` ${
isDesktop() ? styles.desktopApp + ' ' : ''
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
className
}
data-native-file-menu={nativeFileMenuCreated} data-native-file-menu={nativeFileMenuCreated}
style={style} style={style}
> >
@ -47,13 +42,9 @@ export const AppHeader = ({
enableMenu={enableMenu} enableMenu={enableMenu}
project={project?.project} project={project?.project}
file={project?.file} file={project?.file}
/> >
{/* Toolbar if the context deems it */} {projectMenuChildren}
<div className="flex flex-col items-center gap-2"> </ProjectSidebarMenu>
<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>
<div className="flex items-center gap-2 py-1 ml-auto"> <div className="flex items-center gap-2 py-1 ml-auto">
{/* If there are children, show them, otherwise show User menu */} {/* If there are children, show them, otherwise show User menu */}
{children || <CommandBarOpenButton />} {children || <CommandBarOpenButton />}

View File

@ -33,6 +33,7 @@ import { useSettings } from '@src/lib/singletons'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons' import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
import styles from './CommandBarKclInput.module.css' import styles from './CommandBarKclInput.module.css'
import { useModelingContext } from '@src/hooks/useModelingContext'
// TODO: remove the need for this selector once we decouple all actors from React // TODO: remove the need for this selector once we decouple all actors from React
const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) => const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) =>
@ -55,6 +56,9 @@ function CommandBarKclInput({
arg.name arg.name
] as KclCommandValue | undefined ] as KclCommandValue | undefined
const settings = useSettings() const settings = useSettings()
const {
context: { selectionRanges },
} = useModelingContext()
const argMachineContext = useSelector( const argMachineContext = useSelector(
arg.machineActor, arg.machineActor,
machineContextSelector machineContextSelector
@ -126,6 +130,7 @@ function CommandBarKclInput({
value, value,
initialVariableName, initialVariableName,
sourceRange: sourceRangeForPrevVariables, sourceRange: sourceRangeForPrevVariables,
selectionRanges,
}) })
const varMentionData: Completion[] = prevVariables.map((v) => { const varMentionData: Completion[] = prevVariables.map((v) => {

View File

@ -10,13 +10,13 @@ export function CommandBarOpenButton() {
return ( return (
<button <button
type="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' })} onClick={() => commandBarActor.send({ type: 'Open' })}
data-testid="command-bar-open-button" data-testid="command-bar-open-button"
> >
<CustomIcon name="command" className="w-5 h-5" /> <CustomIcon name="command" className="w-5 h-5" />
<span>Commands</span> <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)} {hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
</kbd> </kbd>
</button> </button>

View File

@ -17,40 +17,39 @@ import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { PATHS } from '@src/lib/paths' import { PATHS } from '@src/lib/paths'
import { engineCommandManager, kclManager } from '@src/lib/singletons' 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' import { commandBarActor } from '@src/lib/singletons'
interface ProjectSidebarMenuProps extends React.PropsWithChildren {
enableMenu?: boolean
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
project, project,
file, file,
enableMenu = false, enableMenu = false,
}: { children,
enableMenu?: boolean }: ProjectSidebarMenuProps) => {
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}) => {
// Make room for traffic lights on desktop left side. // Make room for traffic lights on desktop left side.
// TODO: make sure this doesn't look like shit on Linux or Windows // TODO: make sure this doesn't look like shit on Linux or Windows
const trafficLightsOffset = const trafficLightsOffset =
isDesktop() && window.electron.os.isMac ? 'ml-20' : '' isDesktop() && window.electron.os.isMac ? 'ml-20' : ''
return ( return (
<div <div className={'!no-underline flex gap-2 ' + trafficLightsOffset}>
className={
'!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2 ' +
trafficLightsOffset
}
>
<AppLogoLink project={project} file={file} /> <AppLogoLink project={project} file={file} />
{enableMenu ? ( {enableMenu ? (
<ProjectMenuPopover project={project} file={file} /> <ProjectMenuPopover project={project} file={file} />
) : ( ) : (
<span <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" data-testid="project-name"
> >
{project?.name ? project.name : APP_NAME} {project?.name ? project.name : APP_NAME}
</span> </span>
)} )}
{children}
</div> </div>
) )
} }
@ -64,7 +63,7 @@ function AppLogoLink({
}) { }) {
const { onProjectClose } = useLspContext() const { onProjectClose } = useLspContext()
const wrapperClassName = 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' const logoClassName = 'w-auto h-4 text-chalkboard-10'
return isDesktop() ? ( return isDesktop() ? (
@ -238,12 +237,23 @@ function ProjectMenuPopover({
return ( return (
<Popover className="relative"> <Popover className="relative">
<Popover.Button <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" 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 <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" data-testid="app-header-file-name"
> >
{isDesktop() && file?.name {isDesktop() && file?.name
@ -252,14 +262,6 @@ function ProjectMenuPopover({
) )
: APP_NAME} : APP_NAME}
</span> </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> </div>
<CustomIcon <CustomIcon
name="caretDown" name="caretDown"

View File

@ -10,6 +10,7 @@ import {
} from '@src/components/AvailableVarsHelpers' } from '@src/components/AvailableVarsHelpers'
import type { Expr } from '@src/lang/wasm' import type { Expr } from '@src/lang/wasm'
import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression' import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression'
import type { Selections } from '@src/lib/selections'
type ModalResolve = { type ModalResolve = {
value: string value: string
@ -25,6 +26,7 @@ type SetAngleLengthModalProps = InstanceProps<ModalResolve, ModalReject> & {
value: number value: number
valueName: string valueName: string
shouldCreateVariable?: boolean shouldCreateVariable?: boolean
selectionRanges: Selections
} }
export const createSetAngleLengthModal = create< export const createSetAngleLengthModal = create<
@ -40,6 +42,7 @@ export const SetAngleLengthModal = ({
value: initialValue, value: initialValue,
valueName, valueName,
shouldCreateVariable: initialShouldCreateVariable = false, shouldCreateVariable: initialShouldCreateVariable = false,
selectionRanges,
}: SetAngleLengthModalProps) => { }: SetAngleLengthModalProps) => {
const [sign, setSign] = useState(Math.sign(Number(initialValue))) const [sign, setSign] = useState(Math.sign(Number(initialValue)))
const [value, setValue] = useState(String(initialValue * sign)) const [value, setValue] = useState(String(initialValue * sign))
@ -59,6 +62,7 @@ export const SetAngleLengthModal = ({
} = useCalculateKclExpression({ } = useCalculateKclExpression({
value, value,
initialVariableName: valueName, initialVariableName: valueName,
selectionRanges,
}) })
return ( return (

View File

@ -10,6 +10,7 @@ import {
} from '@src/components/AvailableVarsHelpers' } from '@src/components/AvailableVarsHelpers'
import type { Expr } from '@src/lang/wasm' import type { Expr } from '@src/lang/wasm'
import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression' import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression'
import type { Selections } from '@src/lib/selections'
type ModalResolve = { type ModalResolve = {
value: string value: string
@ -27,6 +28,7 @@ type GetInfoModalProps = InstanceProps<ModalResolve, ModalReject> & {
isSegNameEditable: boolean isSegNameEditable: boolean
value?: number value?: number
initialVariableName: string initialVariableName: string
selectionRanges: Selections
} }
export const createInfoModal = create< export const createInfoModal = create<
@ -43,6 +45,7 @@ export const GetInfoModal = ({
isSegNameEditable, isSegNameEditable,
value: initialValue, value: initialValue,
initialVariableName, initialVariableName,
selectionRanges,
}: GetInfoModalProps) => { }: GetInfoModalProps) => {
const [sign, setSign] = useState(Math.sign(Number(initialValue))) const [sign, setSign] = useState(Math.sign(Number(initialValue)))
const [segName, setSegName] = useState(initialSegName) const [segName, setSegName] = useState(initialSegName)
@ -60,7 +63,11 @@ export const GetInfoModal = ({
newVariableName, newVariableName,
isNewVariableNameUnique, isNewVariableNameUnique,
newVariableInsertIndex, newVariableInsertIndex,
} = useCalculateKclExpression({ value: value, initialVariableName }) } = useCalculateKclExpression({
value: value,
initialVariableName,
selectionRanges,
})
return ( return (
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>

View File

@ -6,11 +6,13 @@ import { type InstanceProps, create } from 'react-modal-promise'
import { ActionButton } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton'
import { CreateNewVariable } from '@src/components/AvailableVarsHelpers' import { CreateNewVariable } from '@src/components/AvailableVarsHelpers'
import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression' import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression'
import type { Selections } from '@src/lib/selections'
type ModalResolve = { variableName: string } type ModalResolve = { variableName: string }
type ModalReject = boolean type ModalReject = boolean
type SetVarNameModalProps = InstanceProps<ModalResolve, ModalReject> & { type SetVarNameModalProps = InstanceProps<ModalResolve, ModalReject> & {
valueName: string valueName: string
selectionRanges: Selections
} }
export const createSetVarNameModal = create< export const createSetVarNameModal = create<
@ -24,9 +26,14 @@ export const SetVarNameModal = ({
onResolve, onResolve,
onReject, onReject,
valueName, valueName,
selectionRanges,
}: SetVarNameModalProps) => { }: SetVarNameModalProps) => {
const { isNewVariableNameUnique, newVariableName, setNewVariableName } = const { isNewVariableNameUnique, newVariableName, setNewVariableName } =
useCalculateKclExpression({ value: '', initialVariableName: valueName }) useCalculateKclExpression({
value: '',
initialVariableName: valueName,
selectionRanges,
})
return ( return (
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>

View File

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

View File

@ -1,5 +1,4 @@
import type { Node } from '@rust/kcl-lib/bindings/Node' import type { Node } from '@rust/kcl-lib/bindings/Node'
import { removeDoubleNegatives } from '@src/components/AvailableVarsHelpers' import { removeDoubleNegatives } from '@src/components/AvailableVarsHelpers'
import { import {
GetInfoModal, GetInfoModal,
@ -167,6 +166,7 @@ export async function applyConstraintIntersect({
isSegNameEditable: !tagInfo?.isTagExisting, isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform, value: valueUsedInTransform,
initialVariableName: 'offset', initialVariableName: 'offset',
selectionRanges,
}) })
if ( if (
!variableName && !variableName &&

View File

@ -113,6 +113,7 @@ export async function applyConstraintAbsDistance({
await getModalInfo({ await getModalInfo({
value: forceVal, value: forceVal,
valueName: constraint === 'yAbs' ? 'yDis' : 'xDis', valueName: constraint === 'yAbs' ? 'yDis' : 'xDis',
selectionRanges,
}) })
if (!isExprBinaryPart(valueNode)) if (!isExprBinaryPart(valueNode))
return Promise.reject('Invalid valueNode, is not a BinaryPart') return Promise.reject('Invalid valueNode, is not a BinaryPart')

View File

@ -117,7 +117,8 @@ export async function applyConstraintAngleBetween({
isSegNameEditable: !tagInfo?.isTagExisting, isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform, value: valueUsedInTransform,
initialVariableName: 'angle', initialVariableName: 'angle',
} as any) selectionRanges,
})
if ( if (
segName === tagInfo?.tag && segName === tagInfo?.tag &&
Number(value) === valueUsedInTransform && Number(value) === valueUsedInTransform &&

View File

@ -123,7 +123,8 @@ export async function applyConstraintHorzVertDistance({
isSegNameEditable: !tagInfo?.isTagExisting, isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform, value: valueUsedInTransform,
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis', initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any) selectionRanges,
})
if ( if (
!variableName && !variableName &&
segName === tagInfo?.tag && segName === tagInfo?.tag &&

View File

@ -143,6 +143,7 @@ export async function applyConstraintAngleLength({
value: forceVal, value: forceVal,
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length', valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true, shouldCreateVariable: true,
selectionRanges,
}) })
if (!isExprBinaryPart(valueNode)) if (!isExprBinaryPart(valueNode))
return Promise.reject('Invalid valueNode, is not a BinaryPart') return Promise.reject('Invalid valueNode, is not a BinaryPart')

View File

@ -178,9 +178,9 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
} }
return ( return (
<Popover className="relative"> <Popover className="relative grid">
<Popover.Button <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" data-testid="user-sidebar-toggle"
> >
<div className="flex items-center"> <div className="flex items-center">

View File

@ -52,6 +52,7 @@ export function useConvertToVariable(range?: SourceRange) {
try { try {
const { variableName } = await getVarNameModal({ const { variableName } = await getVarNameModal({
valueName: valueName || 'var', valueName: valueName || 'var',
selectionRanges: context.selectionRanges,
}) })
const { modifiedAst: _modifiedAst, pathToReplacedNode } = const { modifiedAst: _modifiedAst, pathToReplacedNode } =

View File

@ -1,6 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useModelingContext } from '@src/hooks/useModelingContext'
import { useKclContext } from '@src/lang/KclProvider' import { useKclContext } from '@src/lang/KclProvider'
import { findUniqueName } from '@src/lang/create' import { findUniqueName } from '@src/lang/create'
import type { PrevVariable } from '@src/lang/queryAst' import type { PrevVariable } from '@src/lang/queryAst'
@ -12,6 +10,7 @@ import { getCalculatedKclExpressionValue } from '@src/lib/kclHelpers'
import { kclManager } from '@src/lib/singletons' import { kclManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { getInVariableCase } from '@src/lib/utils' import { getInVariableCase } from '@src/lib/utils'
import type { Selections } from '@src/lib/selections'
const isValidVariableName = (name: string) => const isValidVariableName = (name: string) =>
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
@ -25,10 +24,12 @@ export function useCalculateKclExpression({
value, value,
initialVariableName: valueName = '', initialVariableName: valueName = '',
sourceRange, sourceRange,
selectionRanges,
}: { }: {
value: string value: string
initialVariableName?: string initialVariableName?: string
sourceRange?: SourceRange sourceRange?: SourceRange
selectionRanges: Selections
}): { }): {
inputRef: React.RefObject<HTMLInputElement> inputRef: React.RefObject<HTMLInputElement>
valueNode: Expr | null valueNode: Expr | null
@ -45,12 +46,10 @@ export function useCalculateKclExpression({
// has completed // has completed
const [isExecuting, setIsExecuting] = useState(false) const [isExecuting, setIsExecuting] = useState(false)
const { variables, code } = useKclContext() const { variables, code } = useKclContext()
const { context } = useModelingContext()
// If there is no selection, use the end of the code // If there is no selection, use the end of the code
// so all variables are available // so all variables are available
const selectionRange: const selectionRange: SourceRange | undefined =
| (typeof context)['selectionRanges']['graphSelections'][number]['codeRef']['range'] selectionRanges.graphSelections[0]?.codeRef?.range
| undefined = context.selectionRanges.graphSelections[0]?.codeRef?.range
// If there is no selection, use the end of the code // If there is no selection, use the end of the code
// If we don't memoize this, we risk an infinite set/read state loop // If we don't memoize this, we risk an infinite set/read state loop
const endingSourceRange = useMemo( const endingSourceRange = useMemo(

View File

@ -29,7 +29,6 @@ import {
applyConstraintHorzVert, applyConstraintHorzVert,
horzVertInfo, horzVertInfo,
} from '@src/components/Toolbar/HorzVert' } from '@src/components/Toolbar/HorzVert'
import { intersectInfo } from '@src/components/Toolbar/Intersect'
import { import {
applyRemoveConstrainingValues, applyRemoveConstrainingValues,
removeConstrainingValuesInfo, removeConstrainingValuesInfo,
@ -155,6 +154,7 @@ import type { Plane } from '@rust/kcl-lib/bindings/Plane'
import type { Point3d } from '@rust/kcl-lib/bindings/ModelingCmd' import type { Point3d } from '@rust/kcl-lib/bindings/ModelingCmd'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils' import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import { letEngineAnimateAndSyncCamAfter } from '@src/clientSideScene/CameraControls' import { letEngineAnimateAndSyncCamAfter } from '@src/clientSideScene/CameraControls'
import { intersectInfo } from '@src/components/Toolbar/Intersect'
export type SetSelections = export type SetSelections =
| { | {

View File

@ -221,10 +221,7 @@ const Home = () => {
return ( return (
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden"> <div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
<AppHeader <AppHeader nativeFileMenuCreated={nativeFileMenuCreated} />
nativeFileMenuCreated={nativeFileMenuCreated}
showToolbar={false}
/>
<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"> <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 <HomeHeader
setQuery={setQuery} setQuery={setQuery}