Clean up 'prompt user for info' modals (#58)
This commit is contained in:
236
src/components/AvailableVarsHelpers.tsx
Normal file
236
src/components/AvailableVarsHelpers.tsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import { useEffect, useState, useRef } from 'react'
|
||||||
|
import { abstractSyntaxTree, Value } from '../lang/abstractSyntaxTree'
|
||||||
|
import { executor } from '../lang/executor'
|
||||||
|
import { findUniqueName } from '../lang/modifyAst'
|
||||||
|
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||||
|
import { lexer } from '../lang/tokeniser'
|
||||||
|
import { useStore } from '../useStore'
|
||||||
|
|
||||||
|
export const AvailableVars = ({
|
||||||
|
onVarClick,
|
||||||
|
prevVariables,
|
||||||
|
}: {
|
||||||
|
onVarClick: (a: string) => void
|
||||||
|
prevVariables: PrevVariable<any>[]
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ul className="flex flex-col">
|
||||||
|
{prevVariables.length &&
|
||||||
|
prevVariables.map(({ key, value }) => (
|
||||||
|
<li key={key}>
|
||||||
|
<button
|
||||||
|
className="flex w-full justify-between items-center rounded-md hover:bg-gray-100 max-w-xs"
|
||||||
|
onClick={() => onVarClick(key)}
|
||||||
|
>
|
||||||
|
<span className="font-[monospace] text-gray-800">{key}</span>{' '}
|
||||||
|
<span className="font-[monospace] text-gray-600 w-24 text-start font-bold">
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addToInputHelper =
|
||||||
|
(
|
||||||
|
inputRef: React.RefObject<HTMLInputElement>,
|
||||||
|
setValue: (a: string) => void
|
||||||
|
) =>
|
||||||
|
(varName: string) => {
|
||||||
|
const selectionStart = inputRef.current?.selectionStart
|
||||||
|
let selectionEnd = inputRef.current?.selectionEnd
|
||||||
|
let newValue = ''
|
||||||
|
if (
|
||||||
|
typeof selectionStart === 'number' &&
|
||||||
|
typeof selectionEnd === 'number'
|
||||||
|
) {
|
||||||
|
newValue = stringSplice(
|
||||||
|
inputRef.current?.value || '',
|
||||||
|
selectionStart,
|
||||||
|
selectionEnd,
|
||||||
|
varName
|
||||||
|
)
|
||||||
|
selectionEnd = selectionStart + varName.length
|
||||||
|
} else {
|
||||||
|
newValue = inputRef.current?.value + varName
|
||||||
|
}
|
||||||
|
setValue(newValue)
|
||||||
|
inputRef.current?.focus()
|
||||||
|
setTimeout(() => {
|
||||||
|
// run in the next render cycle
|
||||||
|
const _selectionEnd =
|
||||||
|
typeof selectionEnd === 'number' ? selectionEnd : newValue.length
|
||||||
|
inputRef.current?.setSelectionRange(_selectionEnd, _selectionEnd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringSplice(str: string, index: number, count: number, add: string) {
|
||||||
|
return str.slice(0, index) + (add || '') + str.slice(index + count)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCalc({
|
||||||
|
value,
|
||||||
|
initialVariableName: valueName = '',
|
||||||
|
}: {
|
||||||
|
value: string
|
||||||
|
initialVariableName?: string
|
||||||
|
}): {
|
||||||
|
inputRef: React.RefObject<HTMLInputElement>
|
||||||
|
valueNode: Value | null
|
||||||
|
calcResult: string
|
||||||
|
prevVariables: PrevVariable<any>[]
|
||||||
|
newVariableName: string
|
||||||
|
isNewVariableNameUnique: boolean
|
||||||
|
newVariableInsertIndex: number
|
||||||
|
setNewVariableName: (a: string) => void
|
||||||
|
} {
|
||||||
|
const { ast, programMemory, selectionRange } = useStore((s) => ({
|
||||||
|
ast: s.ast,
|
||||||
|
programMemory: s.programMemory,
|
||||||
|
selectionRange: s.selectionRanges[0],
|
||||||
|
}))
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const [availableVarInfo, setAvailableVarInfo] = useState<
|
||||||
|
ReturnType<typeof findAllPreviousVariables>
|
||||||
|
>({
|
||||||
|
variables: [],
|
||||||
|
insertIndex: 0,
|
||||||
|
bodyPath: [],
|
||||||
|
})
|
||||||
|
const [valueNode, setValueNode] = useState<Value | 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)
|
||||||
|
if (ast) {
|
||||||
|
setNewVariableName(findUniqueName(ast, valueName))
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const allVarNames = Object.keys(programMemory.root)
|
||||||
|
if (allVarNames.includes(newVariableName)) {
|
||||||
|
setIsNewVariableNameUnique(false)
|
||||||
|
} else {
|
||||||
|
setIsNewVariableNameUnique(true)
|
||||||
|
}
|
||||||
|
}, [newVariableName])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ast || !programMemory || !selectionRange) return
|
||||||
|
const varInfo = findAllPreviousVariables(ast, programMemory, selectionRange)
|
||||||
|
setAvailableVarInfo(varInfo)
|
||||||
|
}, [ast, programMemory, selectionRange])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const code = `const __result__ = ${value}\nshow(__result__)`
|
||||||
|
const ast = abstractSyntaxTree(lexer(code))
|
||||||
|
const _programMem: any = { root: {} }
|
||||||
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
|
})
|
||||||
|
const programMemory = executor(ast, _programMem)
|
||||||
|
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 = programMemory?.root?.__result__?.value
|
||||||
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
|
init && setValueNode(init)
|
||||||
|
} catch (e) {
|
||||||
|
setCalcResult('NAN')
|
||||||
|
setValueNode(null)
|
||||||
|
}
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
return {
|
||||||
|
valueNode,
|
||||||
|
calcResult,
|
||||||
|
prevVariables: availableVarInfo.variables,
|
||||||
|
newVariableInsertIndex: availableVarInfo.insertIndex,
|
||||||
|
newVariableName,
|
||||||
|
isNewVariableNameUnique,
|
||||||
|
setNewVariableName,
|
||||||
|
inputRef,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CalcResult = ({ calcResult }: { calcResult: string }) => {
|
||||||
|
return (
|
||||||
|
<div className="font-[monospace] pl-4 text-gray-600">
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
calcResult === 'NAN' ? 'bg-pink-200' : ''
|
||||||
|
} px-2 py-0.5 rounded`}
|
||||||
|
>
|
||||||
|
= {calcResult}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateNewVariable = ({
|
||||||
|
newVariableName,
|
||||||
|
isNewVariableNameUnique,
|
||||||
|
setNewVariableName,
|
||||||
|
shouldCreateVariable,
|
||||||
|
setShouldCreateVariable,
|
||||||
|
}: {
|
||||||
|
isNewVariableNameUnique: boolean
|
||||||
|
newVariableName: string
|
||||||
|
setNewVariableName: (a: string) => void
|
||||||
|
shouldCreateVariable: boolean
|
||||||
|
setShouldCreateVariable: (a: boolean) => void
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
htmlFor="create-new-variable"
|
||||||
|
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
|
||||||
|
>
|
||||||
|
Create new variable
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 flex flex-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1 flex-shrink"
|
||||||
|
checked={shouldCreateVariable}
|
||||||
|
onChange={(e) => {
|
||||||
|
setShouldCreateVariable(e.target.checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
disabled={!shouldCreateVariable}
|
||||||
|
name="create-new-variable"
|
||||||
|
id="create-new-variable"
|
||||||
|
className={`shadow-sm font-[monospace] focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1 flex-shrink-0 ${
|
||||||
|
!shouldCreateVariable ? 'opacity-50' : ''
|
||||||
|
}`}
|
||||||
|
value={newVariableName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNewVariableName(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!isNewVariableNameUnique && (
|
||||||
|
<div className="bg-pink-200 rounded px-2 py-0.5 text-xs">
|
||||||
|
Sorry, that's not a unique variable name. Please try something else
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
142
src/components/SetAngleLengthModal.tsx
Normal file
142
src/components/SetAngleLengthModal.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
|
import { Fragment, useState } from 'react'
|
||||||
|
import { Value } from '../lang/abstractSyntaxTree'
|
||||||
|
import {
|
||||||
|
AvailableVars,
|
||||||
|
addToInputHelper,
|
||||||
|
useCalc,
|
||||||
|
CalcResult,
|
||||||
|
CreateNewVariable,
|
||||||
|
} from './AvailableVarsHelpers'
|
||||||
|
|
||||||
|
export const SetAngleLengthModal = ({
|
||||||
|
isOpen,
|
||||||
|
onResolve,
|
||||||
|
onReject,
|
||||||
|
value: initialValue,
|
||||||
|
valueName,
|
||||||
|
}: {
|
||||||
|
isOpen: boolean
|
||||||
|
onResolve: (a: {
|
||||||
|
value: string
|
||||||
|
valueNode: Value
|
||||||
|
variableName?: string
|
||||||
|
newVariableInsertIndex: number
|
||||||
|
}) => void
|
||||||
|
onReject: (a: any) => void
|
||||||
|
value: number
|
||||||
|
valueName: string
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState(String(initialValue))
|
||||||
|
const [shouldCreateVariable, setShouldCreateVariable] = useState(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
prevVariables,
|
||||||
|
calcResult,
|
||||||
|
valueNode,
|
||||||
|
isNewVariableNameUnique,
|
||||||
|
newVariableName,
|
||||||
|
setNewVariableName,
|
||||||
|
inputRef,
|
||||||
|
newVariableInsertIndex,
|
||||||
|
} = useCalc({ value, initialVariableName: valueName })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-10" onClose={onReject}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||||
|
<Dialog.Title
|
||||||
|
as="h3"
|
||||||
|
className="text-lg font-medium leading-6 text-gray-900 capitalize"
|
||||||
|
>
|
||||||
|
Set {valueName}
|
||||||
|
</Dialog.Title>
|
||||||
|
<div className="block text-sm font-medium text-gray-700 mt-3 font-mono capitalize">
|
||||||
|
Available Variables
|
||||||
|
</div>
|
||||||
|
<AvailableVars
|
||||||
|
prevVariables={prevVariables}
|
||||||
|
onVarClick={addToInputHelper(inputRef, setValue)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="val"
|
||||||
|
className="block text-sm font-medium text-gray-700 mt-3 font-mono capitalize"
|
||||||
|
>
|
||||||
|
{valueName} Value
|
||||||
|
</label>
|
||||||
|
<div className="mt-1">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
name="val"
|
||||||
|
id="val"
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CalcResult calcResult={calcResult} />
|
||||||
|
<CreateNewVariable
|
||||||
|
setNewVariableName={setNewVariableName}
|
||||||
|
newVariableName={newVariableName}
|
||||||
|
isNewVariableNameUnique={isNewVariableNameUnique}
|
||||||
|
shouldCreateVariable={shouldCreateVariable}
|
||||||
|
setShouldCreateVariable={setShouldCreateVariable}
|
||||||
|
/>
|
||||||
|
<div className="mt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={calcResult === 'NAN' || !isNewVariableNameUnique}
|
||||||
|
className={`inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 ${
|
||||||
|
calcResult === 'NAN' || !isNewVariableNameUnique
|
||||||
|
? 'opacity-50 cursor-not-allowed'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
onClick={() =>
|
||||||
|
valueNode &&
|
||||||
|
onResolve({
|
||||||
|
value,
|
||||||
|
valueNode,
|
||||||
|
newVariableInsertIndex,
|
||||||
|
variableName: shouldCreateVariable
|
||||||
|
? newVariableName
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add constraining value
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
)
|
||||||
|
}
|
@ -1,272 +0,0 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react'
|
|
||||||
import { Fragment, useState, useRef, useEffect } from 'react'
|
|
||||||
import { abstractSyntaxTree, Value } from '../lang/abstractSyntaxTree'
|
|
||||||
import { executor } from '../lang/executor'
|
|
||||||
import { findUniqueName } from '../lang/modifyAst'
|
|
||||||
import { PrevVariable } from '../lang/queryAst'
|
|
||||||
import { lexer } from '../lang/tokeniser'
|
|
||||||
import { useStore } from '../useStore'
|
|
||||||
|
|
||||||
export const SetAngleLengthModal = ({
|
|
||||||
isOpen,
|
|
||||||
onResolve,
|
|
||||||
onReject,
|
|
||||||
prevVariables,
|
|
||||||
value: initialValue,
|
|
||||||
valueName,
|
|
||||||
}: {
|
|
||||||
isOpen: boolean
|
|
||||||
onResolve: (a: {
|
|
||||||
value: string
|
|
||||||
valueNode: Value
|
|
||||||
variableName?: string
|
|
||||||
}) => void
|
|
||||||
onReject: (a: any) => void
|
|
||||||
prevVariables: PrevVariable<number>[]
|
|
||||||
value: number
|
|
||||||
valueName: string
|
|
||||||
}) => {
|
|
||||||
const { ast, programMemory } = useStore((s) => ({
|
|
||||||
ast: s.ast,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
}))
|
|
||||||
const [value, setValue] = useState(String(initialValue))
|
|
||||||
const [calcResult, setCalcResult] = useState('NAN')
|
|
||||||
const [shouldCreateVariable, setShouldCreateVariable] = useState(false)
|
|
||||||
const [newVariableName, setNewVariableName] = useState('')
|
|
||||||
const [isNewVariableNameUnique, setIsNewVariableNameUnique] = useState(true)
|
|
||||||
const [valueNode, setValueNode] = useState<any>(null)
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
inputRef.current && inputRef.current.focus()
|
|
||||||
inputRef.current &&
|
|
||||||
inputRef.current.setSelectionRange(0, String(value).length)
|
|
||||||
}, 100)
|
|
||||||
if (ast) {
|
|
||||||
setNewVariableName(findUniqueName(ast, valueName))
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const allVarNames = Object.keys(programMemory.root)
|
|
||||||
if (allVarNames.includes(newVariableName)) {
|
|
||||||
setIsNewVariableNameUnique(false)
|
|
||||||
} else {
|
|
||||||
setIsNewVariableNameUnique(true)
|
|
||||||
}
|
|
||||||
}, [newVariableName])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
const code = `const __result__ = ${value}\nshow(__result__)`
|
|
||||||
const ast = abstractSyntaxTree(lexer(code))
|
|
||||||
const _programMem: any = { root: {} }
|
|
||||||
prevVariables.forEach(({ key, value }) => {
|
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
|
||||||
})
|
|
||||||
const programMemory = executor(ast, _programMem)
|
|
||||||
const resultDeclaration = ast.body.find(
|
|
||||||
(a) =>
|
|
||||||
a.type === 'VariableDeclaration' &&
|
|
||||||
a.declarations?.[0]?.id?.name === '__result__'
|
|
||||||
)
|
|
||||||
const init =
|
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
|
||||||
resultDeclaration?.declarations?.[0]?.init
|
|
||||||
console.log(init)
|
|
||||||
setCalcResult(programMemory?.root?.__result__?.value || 'NAN')
|
|
||||||
setValueNode(init)
|
|
||||||
} catch (e) {
|
|
||||||
setCalcResult('NAN')
|
|
||||||
setValueNode(null)
|
|
||||||
}
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-10" onClose={onReject}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0 scale-95"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100 scale-100"
|
|
||||||
leaveTo="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-gray-900 capitalize"
|
|
||||||
>
|
|
||||||
Set {valueName}
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="block text-sm font-medium text-gray-700 mt-3 font-mono capitalize">
|
|
||||||
Available Variables
|
|
||||||
</div>
|
|
||||||
<ul className="flex flex-col">
|
|
||||||
{prevVariables.length &&
|
|
||||||
prevVariables.map(({ key, value }) => (
|
|
||||||
<li key={key}>
|
|
||||||
<button
|
|
||||||
className="flex w-full justify-between items-center rounded-md hover:bg-gray-100 max-w-xs"
|
|
||||||
onClick={(e) => {
|
|
||||||
const selectionStart =
|
|
||||||
inputRef.current?.selectionStart
|
|
||||||
let selectionEnd = inputRef.current?.selectionEnd
|
|
||||||
let newValue = ''
|
|
||||||
if (
|
|
||||||
typeof selectionStart === 'number' &&
|
|
||||||
typeof selectionEnd === 'number'
|
|
||||||
) {
|
|
||||||
newValue = stringSplice(
|
|
||||||
inputRef.current?.value || '',
|
|
||||||
selectionStart,
|
|
||||||
selectionEnd,
|
|
||||||
key
|
|
||||||
)
|
|
||||||
selectionEnd = selectionStart + key.length
|
|
||||||
} else {
|
|
||||||
newValue = inputRef.current?.value + key
|
|
||||||
}
|
|
||||||
setValue(newValue)
|
|
||||||
inputRef.current?.focus()
|
|
||||||
setTimeout(() => {
|
|
||||||
// run in the next render cycle
|
|
||||||
const _selectionEnd =
|
|
||||||
typeof selectionEnd === 'number'
|
|
||||||
? selectionEnd
|
|
||||||
: newValue.length
|
|
||||||
inputRef.current?.setSelectionRange(
|
|
||||||
_selectionEnd,
|
|
||||||
_selectionEnd
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="font-[monospace] text-gray-800">
|
|
||||||
{key}
|
|
||||||
</span>{' '}
|
|
||||||
<span className="font-[monospace] text-gray-600 w-24 text-start font-bold">
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<label
|
|
||||||
htmlFor="val"
|
|
||||||
className="block text-sm font-medium text-gray-700 mt-3 font-mono capitalize"
|
|
||||||
>
|
|
||||||
{valueName} Value
|
|
||||||
</label>
|
|
||||||
<div className="mt-1">
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
name="val"
|
|
||||||
id="val"
|
|
||||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1"
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => {
|
|
||||||
setValue(e.target.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="font-[monospace] pl-4 text-gray-600">
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
calcResult === 'NAN' ? 'bg-pink-200' : ''
|
|
||||||
} px-2 py-0.5 rounded`}
|
|
||||||
>
|
|
||||||
= {calcResult}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label
|
|
||||||
htmlFor="val"
|
|
||||||
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
|
|
||||||
>
|
|
||||||
Create new variable
|
|
||||||
</label>
|
|
||||||
<div className="mt-1 flex flex-1">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="val"
|
|
||||||
id="val"
|
|
||||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1 flex-shrink"
|
|
||||||
checked={shouldCreateVariable}
|
|
||||||
onChange={(e) => {
|
|
||||||
setShouldCreateVariable(e.target.checked)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
disabled={!shouldCreateVariable}
|
|
||||||
name="val"
|
|
||||||
id="val"
|
|
||||||
className={`shadow-sm font-[monospace] focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1 flex-shrink-0 ${
|
|
||||||
!shouldCreateVariable ? 'opacity-50' : ''
|
|
||||||
}`}
|
|
||||||
value={newVariableName}
|
|
||||||
onChange={(e) => {
|
|
||||||
setNewVariableName(e.target.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{!isNewVariableNameUnique && (
|
|
||||||
<div className="bg-pink-200 rounded px-2 py-0.5 text-xs">
|
|
||||||
Sorry, that's not a unique variable name. Please try
|
|
||||||
something else
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-4">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={calcResult === 'NAN' || !isNewVariableNameUnique}
|
|
||||||
className={`inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 ${
|
|
||||||
calcResult === 'NAN' || !isNewVariableNameUnique
|
|
||||||
? 'opacity-50 cursor-not-allowed'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
onClick={() =>
|
|
||||||
onResolve({
|
|
||||||
value,
|
|
||||||
valueNode,
|
|
||||||
variableName: shouldCreateVariable
|
|
||||||
? newVariableName
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Add constraining value
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringSplice(str: string, index: number, count: number, add: string) {
|
|
||||||
return str.slice(0, index) + (add || '') + str.slice(index + count)
|
|
||||||
}
|
|
@ -1,28 +1,55 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react'
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
|
import { Value } from '../lang/abstractSyntaxTree'
|
||||||
|
import {
|
||||||
|
AvailableVars,
|
||||||
|
addToInputHelper,
|
||||||
|
useCalc,
|
||||||
|
CalcResult,
|
||||||
|
CreateNewVariable,
|
||||||
|
} from './AvailableVarsHelpers'
|
||||||
|
|
||||||
export const GetInfoModal = ({
|
export const GetInfoModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onResolve,
|
onResolve,
|
||||||
onReject,
|
onReject,
|
||||||
// fields: initialFields,
|
|
||||||
segName: initialSegName,
|
segName: initialSegName,
|
||||||
isSegNameEditable,
|
isSegNameEditable,
|
||||||
value: initialValue,
|
value: initialValue,
|
||||||
|
initialVariableName,
|
||||||
}: {
|
}: {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onResolve: (a: any) => void
|
onResolve: (a: {
|
||||||
|
value: string
|
||||||
|
segName: string
|
||||||
|
valueNode: Value
|
||||||
|
variableName?: string
|
||||||
|
newVariableInsertIndex: number
|
||||||
|
}) => void
|
||||||
onReject: (a: any) => void
|
onReject: (a: any) => void
|
||||||
segName: string
|
segName: string
|
||||||
isSegNameEditable: boolean
|
isSegNameEditable: boolean
|
||||||
value: number
|
value: number
|
||||||
|
initialVariableName: string
|
||||||
}) => {
|
}) => {
|
||||||
const [segName, setSegName] = useState(initialSegName)
|
const [segName, setSegName] = useState(initialSegName)
|
||||||
const [value, setValue] = useState(initialValue)
|
const [value, setValue] = useState(String(initialValue))
|
||||||
|
const [shouldCreateVariable, setShouldCreateVariable] = useState(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
prevVariables,
|
||||||
|
inputRef,
|
||||||
|
calcResult,
|
||||||
|
valueNode,
|
||||||
|
setNewVariableName,
|
||||||
|
newVariableName,
|
||||||
|
isNewVariableNameUnique,
|
||||||
|
newVariableInsertIndex,
|
||||||
|
} = useCalc({ value, initialVariableName })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-10" onClose={onResolve}>
|
<Dialog as="div" className="relative z-10" onClose={onReject}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
@ -53,6 +80,13 @@ export const GetInfoModal = ({
|
|||||||
>
|
>
|
||||||
Constraint details
|
Constraint details
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
|
<div className="block text-sm font-medium text-gray-700 mt-3 font-mono capitalize">
|
||||||
|
Available Variables
|
||||||
|
</div>
|
||||||
|
<AvailableVars
|
||||||
|
prevVariables={prevVariables}
|
||||||
|
onVarClick={addToInputHelper(inputRef, setValue)}
|
||||||
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="val"
|
htmlFor="val"
|
||||||
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
|
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
|
||||||
@ -61,16 +95,18 @@ export const GetInfoModal = ({
|
|||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
name="val"
|
name="val"
|
||||||
id="val"
|
id="val"
|
||||||
|
ref={inputRef}
|
||||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono"
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue(Number(e.target.value))
|
setValue(e.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<CalcResult calcResult={calcResult} />
|
||||||
<label
|
<label
|
||||||
htmlFor="segName"
|
htmlFor="segName"
|
||||||
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
|
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
|
||||||
@ -90,12 +126,30 @@ export const GetInfoModal = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<CreateNewVariable
|
||||||
|
setNewVariableName={setNewVariableName}
|
||||||
|
newVariableName={newVariableName}
|
||||||
|
isNewVariableNameUnique={isNewVariableNameUnique}
|
||||||
|
shouldCreateVariable={shouldCreateVariable}
|
||||||
|
setShouldCreateVariable={setShouldCreateVariable}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
onClick={() => onResolve({ segName, value })}
|
onClick={() =>
|
||||||
|
valueNode &&
|
||||||
|
onResolve({
|
||||||
|
segName,
|
||||||
|
value,
|
||||||
|
valueNode,
|
||||||
|
newVariableInsertIndex,
|
||||||
|
variableName: shouldCreateVariable
|
||||||
|
? newVariableName
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Add constraining value
|
Add constraining value
|
||||||
</button>
|
</button>
|
@ -12,7 +12,7 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { SetAngleLengthModal } from '../SetAngleModal'
|
import { SetAngleLengthModal } from '../SetAngleLengthModal'
|
||||||
import {
|
import {
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
@ -69,18 +69,12 @@ export const SetAngleLength = ({
|
|||||||
programMemory,
|
programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
const availableVarInfo = findAllPreviousVariables(
|
|
||||||
modifiedAst,
|
|
||||||
programMemory,
|
|
||||||
selectionRanges[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { valueNode, variableName } = await getModalInfo({
|
const { valueNode, variableName, newVariableInsertIndex } =
|
||||||
value: valueUsedInTransform,
|
await getModalInfo({
|
||||||
prevVariables: availableVarInfo.variables,
|
value: valueUsedInTransform,
|
||||||
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
|
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const { modifiedAst: _modifiedAst } = transformAstSketchLines({
|
const { modifiedAst: _modifiedAst } = transformAstSketchLines({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(ast)),
|
||||||
@ -95,7 +89,7 @@ export const SetAngleLength = ({
|
|||||||
if (variableName) {
|
if (variableName) {
|
||||||
const newBody = [..._modifiedAst.body]
|
const newBody = [..._modifiedAst.body]
|
||||||
newBody.splice(
|
newBody.splice(
|
||||||
availableVarInfo.insertIndex,
|
newVariableInsertIndex,
|
||||||
0,
|
0,
|
||||||
createVariableDeclaration(variableName, valueNode)
|
createVariableDeclaration(variableName, valueNode)
|
||||||
)
|
)
|
||||||
|
@ -12,8 +12,11 @@ import {
|
|||||||
transformSecondarySketchLinesTagFirst,
|
transformSecondarySketchLinesTagFirst,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { GetInfoModal } from '../GetInfoModal'
|
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createLiteral } from '../../lang/modifyAst'
|
import {
|
||||||
|
createIdentifier,
|
||||||
|
createVariableDeclaration,
|
||||||
|
} from '../../lang/modifyAst'
|
||||||
|
|
||||||
const getModalInfo = create(GetInfoModal as any)
|
const getModalInfo = create(GetInfoModal as any)
|
||||||
|
|
||||||
@ -87,25 +90,50 @@ export const SetHorzDistance = ({
|
|||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory,
|
||||||
})
|
})
|
||||||
const { segName, value }: { segName: string; value: number } =
|
const {
|
||||||
await getModalInfo({
|
segName,
|
||||||
segName: tagInfo?.tag,
|
value,
|
||||||
isSegNameEditable: !tagInfo?.isTagExisting,
|
valueNode,
|
||||||
value: valueUsedInTransform,
|
variableName,
|
||||||
} as any)
|
newVariableInsertIndex,
|
||||||
|
}: {
|
||||||
|
segName: string
|
||||||
|
value: number
|
||||||
|
valueNode: Value
|
||||||
|
variableName?: string
|
||||||
|
newVariableInsertIndex: number
|
||||||
|
} = await getModalInfo({
|
||||||
|
segName: tagInfo?.tag,
|
||||||
|
isSegNameEditable: !tagInfo?.isTagExisting,
|
||||||
|
value: valueUsedInTransform,
|
||||||
|
initialVariableName:
|
||||||
|
horOrVert === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||||
|
} as any)
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst)
|
updateAst(modifiedAst)
|
||||||
} else {
|
} else {
|
||||||
// transform again but forcing certain values
|
// transform again but forcing certain values
|
||||||
const { modifiedAst } = transformSecondarySketchLinesTagFirst({
|
const { modifiedAst: _modifiedAst } =
|
||||||
ast,
|
transformSecondarySketchLinesTagFirst({
|
||||||
selectionRanges,
|
ast,
|
||||||
transformInfos,
|
selectionRanges,
|
||||||
programMemory,
|
transformInfos,
|
||||||
forceSegName: segName,
|
programMemory,
|
||||||
forceValueUsedInTransform: createLiteral(value),
|
forceSegName: segName,
|
||||||
})
|
forceValueUsedInTransform: variableName
|
||||||
updateAst(modifiedAst)
|
? createIdentifier(variableName)
|
||||||
|
: valueNode,
|
||||||
|
})
|
||||||
|
if (variableName) {
|
||||||
|
const newBody = [..._modifiedAst.body]
|
||||||
|
newBody.splice(
|
||||||
|
newVariableInsertIndex,
|
||||||
|
0,
|
||||||
|
createVariableDeclaration(variableName, valueNode)
|
||||||
|
)
|
||||||
|
_modifiedAst.body = newBody
|
||||||
|
}
|
||||||
|
updateAst(_modifiedAst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
Reference in New Issue
Block a user