Files
modeling-app/src/components/CommandBar/CommandBarKclInput.tsx
Frank Noirot b15aac9f48 Move length and named value constraint flows into command palette (#4675)
* Extend KCL argument input

* Migrate length constraint to be a command

* Add ability for `kcl` arguments to provide an initial variable name

* Move named variable flow into command palette

* Fix one e2e test

* Remove unwanted `ZERO` behavior when length constraint has no `variableName`

* Fix issue with `getSelectionCountByType` with sketches not yet in artifactGraph

* Update broken constraint tests

* Look at this (photo)Graph *in the voice of Nickelback*

* Fix segment overlays tests, which had out-of-date selectors

* Return early from `useConvertToVariable` if no selectionRanges

* Fixup for review comment from #4677 (#4696)

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Invalidate nightly bucket files after publish (#4627)

* Invalidate nightly bucket files after publish

* Fix conflict resolution

* Add some more warnings (#4697)

* Add installation instructions for all platforms (#4592)

* Add installation instructions for all platforms
Fixes #4511

* Typo

* Typo2

* Improve linux instructions, thanks @TomPridham

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>

* Bump node to v22.12.0 (LTS) (#4706)

* Point-and-click Shell (#4666)

* WIP: experimenting with Loft UI
Relates to #4470

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Add selection guard

* Working loft for two sketches in the right hardcoded order

* First pass at handling more than 2 sketches

* WIP selections

* WIP selections

* More checks

* Appends the loft line after the 'last' sketch in the code

* Clean up

* Enable multiple selections after the button click

* First point-click loft test (not working locally, loft gets inserted at the wrong place)

* Lint

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Clean up and working pw test

* Add test for doesSceneHaveSweepableSketch with count = 2

* Clean up loftSketches function

* Add pw test for preselected sketches

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Move to fromPromise-based Actor

* Move error logic out of loftSketches, fix pw tests

* Remove comments

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Fix typo

* Revert snapshots

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* WIP: initial shell code addition

* Rollback pw values to pre cam change

* WIP: more additions

* WIP: closer

* WIP: first time working shell mod

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Add extrude lookup for more generic shell

* Handle walls

* Add pw tests for cap shell

* Add shell wall test

* Fix lint

* Add selection guard and clean up

* Lint fix

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* WIP mutliple faces

* WIP circular dep

* Lint

* Look at this (photo)Graph *in the voice of Nickelback*

* Trigger CI

* Working multi-face shell across types

* Cap and wall pw test

* Apply suggestions from Frank's review

Co-authored-by: Frank Noirot <frank@zoo.dev>

* Fix test annotations

* Add unit tests for doesSceneHaveExtrudedSketch

* Manual resolution of snapshot conflicts

* Fix assertParse

* Updated pathToNode construct

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>

* More aggressive using of cache on engine settings changes (#4691)

* move around the files for cache to better localtions

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* udpates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* ensure we can change the grid setting via the command bar

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* pass thru all setttings

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix playwright test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* emoty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix use of `as`

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-12-09 21:43:58 +00:00

261 lines
8.1 KiB
TypeScript

import { Completion } from '@codemirror/autocomplete'
import { EditorView, ViewUpdate } from '@codemirror/view'
import { CustomIcon } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
import { roundOff } from 'lib/utils'
import { varMentions } from 'lib/varCompletionExtension'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react'
const machineContextSelector = (snapshot?: {
context: Record<string, unknown>
}) => snapshot?.context
function CommandBarKclInput({
arg,
stepBack,
onSubmit,
}: {
arg: CommandArgument<unknown> & {
inputType: 'kcl'
name: string
}
stepBack: () => void
onSubmit: (event: unknown) => void
}) {
const { commandBarSend, commandBarState } = useCommandsContext()
const previouslySetValue = commandBarState.context.argumentsToSubmit[
arg.name
] as KclCommandValue | undefined
const { settings } = useSettingsAuthContext()
const argMachineContext = useSelector(
arg.machineActor,
machineContextSelector
)
const defaultValue = useMemo(
() =>
arg.defaultValue
? arg.defaultValue instanceof Function
? arg.defaultValue(commandBarState.context, argMachineContext)
: arg.defaultValue
: '',
[arg.defaultValue, commandBarState.context, argMachineContext]
)
const initialVariableName = useMemo(() => {
// Use the configured variable name if it exists
if (arg.variableName !== undefined) {
return arg.variableName instanceof Function
? arg.variableName(commandBarState.context, argMachineContext)
: arg.variableName
}
// or derive it from the previously set value or the argument name
return previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name
}, [
arg.variableName,
commandBarState.context,
argMachineContext,
arg.name,
previouslySetValue,
])
const [value, setValue] = useState(
previouslySetValue?.valueText || defaultValue || ''
)
const [createNewVariable, setCreateNewVariable] = useState(
(previouslySetValue && 'variableName' in previouslySetValue) ||
arg.createVariableByDefault ||
false
)
const [canSubmit, setCanSubmit] = useState(true)
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
const editorRef = useRef<HTMLDivElement>(null)
const {
prevVariables,
calcResult,
newVariableInsertIndex,
valueNode,
newVariableName,
setNewVariableName,
isNewVariableNameUnique,
} = useCalculateKclExpression({
value,
initialVariableName,
})
const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key,
detail: String(roundOff(v.value as number)),
}))
const { setContainer } = useCodeMirror({
container: editorRef.current,
initialDocValue: value,
autoFocus: true,
selection: {
anchor: 0,
head:
previouslySetValue && 'valueText' in previouslySetValue
? previouslySetValue.valueText.length
: defaultValue.length,
},
theme:
settings.context.app.theme.current === 'system'
? getSystemTheme()
: settings.context.app.theme.current,
extensions: [
EditorView.domEventHandlers({
keydown: (event) => {
if (event.key === 'Backspace' && value === '') {
event.preventDefault()
stepBack()
} else if (event.key === 'Enter') {
event.preventDefault()
handleSubmit()
}
},
}),
varMentions(varMentionData),
EditorView.updateListener.of((vu: ViewUpdate) => {
if (vu.docChanged) {
setValue(vu.state.doc.toString())
}
}),
],
})
useEffect(() => {
if (editorRef.current) {
setContainer(editorRef.current)
}
}, [arg, editorRef])
useEffect(() => {
setCanSubmit(
calcResult !== 'NAN' && (!createNewVariable || isNewVariableNameUnique)
)
}, [calcResult, createNewVariable, isNewVariableNameUnique])
function handleSubmit(e?: React.FormEvent<HTMLFormElement>) {
e?.preventDefault()
if (!canSubmit || valueNode === null) return
onSubmit(
createNewVariable
? ({
valueAst: valueNode,
valueText: value,
valueCalculated: calcResult,
variableName: newVariableName,
insertIndex: newVariableInsertIndex,
variableIdentifierAst: createIdentifier(newVariableName),
variableDeclarationAst: createVariableDeclaration(
newVariableName,
valueNode
),
} satisfies KclCommandValue)
: ({
valueAst: valueNode,
valueText: value,
valueCalculated: calcResult,
} satisfies KclCommandValue)
)
}
return (
<form id="arg-form" onSubmit={handleSubmit} data-can-submit={canSubmit}>
<label className="flex gap-4 items-center mx-4 my-4 border-solid border-b border-chalkboard-50">
<span
data-testid="cmd-bar-arg-name"
className="capitalize text-chalkboard-80 dark:text-chalkboard-20"
>
{arg.name}
</span>
<div
data-testid="cmd-bar-arg-value"
ref={editorRef}
className={styles.editor}
/>
<CustomIcon
name="equal"
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
/>
<span
className={
calcResult === 'NAN'
? 'text-destroy-80 dark:text-destroy-40'
: 'text-succeed-80 dark:text-succeed-40'
}
>
{calcResult === 'NAN'
? "Can't calculate"
: roundOff(Number(calcResult), 4)}
</span>
</label>
{createNewVariable ? (
<div className="flex items-baseline gap-4 mx-4 border-solid border-0 border-b border-chalkboard-50">
<label
htmlFor="variable-name"
className="text-base text-chalkboard-80 dark:text-chalkboard-20"
>
Variable name
</label>
<input
type="text"
id="variable-name"
name="variable-name"
className="flex-1 border-none bg-transparent focus:outline-none"
placeholder="Variable name"
value={newVariableName}
autoCapitalize="off"
autoCorrect="off"
autoComplete="off"
spellCheck="false"
autoFocus
onChange={(e) => setNewVariableName(e.target.value)}
onKeyDown={(e) => {
if (e.currentTarget.value === '' && e.key === 'Backspace') {
setCreateNewVariable(false)
}
}}
onKeyUp={(e) => {
if (e.key === 'Enter') {
handleSubmit()
}
}}
/>
<span
className={
isNewVariableNameUnique
? 'text-succeed-60 dark:text-succeed-40'
: 'text-destroy-60 dark:text-destroy-40'
}
>
{isNewVariableNameUnique ? 'Available' : 'Unavailable'}
</span>
</div>
) : (
<div className="flex justify-between gap-2 px-4">
<button
onClick={() => setCreateNewVariable(true)}
className="text-blue border-none bg-transparent font-sm flex gap-1 items-center pl-0 pr-1"
>
<CustomIcon name="plus" className="w-5 h-5" />
Create new variable
</button>
</div>
)}
</form>
)
}
export default CommandBarKclInput