Files
modeling-app/src/lib/useCalculateKclExpression.ts
Frank Noirot 4423ae16dc Add offset plane point-and-click user flow (#4552)
* Add a code mod for offset plane

* Add support for default plane selections to our `otherSelections` object

* Make availableVars work without a selection range
(because default planes don't have one)

* Make default planes selectable in cmdbar even if AST is empty

* Add offset plane command and activate in toolbar

* Avoid unnecessary error when sketching on offset plane by returning early

* Add supporting test features for offset plane E2E test

* Add WIP E2E test for offset plane
Struggling to get local electron test suite running properly

* Typos

* Lints

* Fix test by making it a web-based one:
I couldn't use the cmdBar fixture with an electron test for some reason.

* Update src/lib/commandBarConfigs/modelingCommandConfig.ts

* Update src/machines/modelingMachine.ts

* Revert changes to `homePageFixture`, as they were unused

* @Irev-Dev feedback: convert action to actor, fix machine layout

* Update plane icon to be not dashed, follow conventions closer
2024-11-26 16:36:14 +00:00

140 lines
4.6 KiB
TypeScript

import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager, engineCommandManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { findUniqueName } from 'lang/modifyAst'
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
import { ProgramMemory, Expr, parse } from 'lang/wasm'
import { useEffect, useRef, useState } from 'react'
import { executeAst } from 'lang/langHelpers'
import { err, trap } from 'lib/trap'
const isValidVariableName = (name: string) =>
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
/**
* Given a value and a possible variablename,
* return helpers for calculating the value and inserting it into the code
* as well as information about the variables that are available
*/
export function useCalculateKclExpression({
value,
initialVariableName: valueName = '',
}: {
value: string
initialVariableName?: string
}): {
inputRef: React.RefObject<HTMLInputElement>
valueNode: Expr | null
calcResult: string
prevVariables: PrevVariable<unknown>[]
newVariableName: string
isNewVariableNameUnique: boolean
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { programMemory, code } = useKclContext()
const { context } = useModelingContext()
// If there is no selection, use the end of the code
// so all variables are available
const selectionRange:
| (typeof context)['selectionRanges']['graphSelections'][number]['codeRef']['range']
| undefined = context.selectionRanges.graphSelections[0]?.codeRef?.range
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
>({
variables: [],
insertIndex: 0,
bodyPath: [],
})
const [valueNode, setValueNode] = useState<Expr | null>(null)
const [calcResult, setCalcResult] = useState('NAN')
const [newVariableName, setNewVariableName] = useState('')
const [isNewVariableNameUnique, setIsNewVariableNameUnique] = useState(true)
useEffect(() => {
setTimeout(() => {
inputRef.current && inputRef.current.focus()
inputRef.current &&
inputRef.current.setSelectionRange(0, String(value).length)
}, 100)
setNewVariableName(findUniqueName(kclManager.ast, valueName))
}, [])
useEffect(() => {
if (
programMemory.has(newVariableName) ||
newVariableName === '' ||
!isValidVariableName(newVariableName)
) {
setIsNewVariableNameUnique(false)
} else {
setIsNewVariableNameUnique(true)
}
}, [programMemory, newVariableName])
useEffect(() => {
if (!programMemory) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
kclManager.programMemory,
// If there is no selection, use the end of the code
selectionRange || [code.length, code.length]
)
setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
useEffect(() => {
const execAstAndSetResult = async () => {
const _code = `const __result__ = ${value}`
const ast = parse(_code)
if (err(ast)) return
if (trap(ast, { suppress: true })) return
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
type: 'String',
value,
__meta: [],
})
if (trap(error, { suppress: true })) return
}
const { execState } = await executeAst({
ast,
engineCommandManager,
useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
})
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
}
if (!value) return
execAstAndSetResult().catch(() => {
setCalcResult('NAN')
setValueNode(null)
})
}, [value, availableVarInfo, code, kclManager.programMemory])
return {
valueNode,
calcResult,
prevVariables: availableVarInfo.variables,
newVariableInsertIndex: availableVarInfo.insertIndex,
newVariableName,
isNewVariableNameUnique,
setNewVariableName,
inputRef,
}
}