import { useRef, useState } from 'react' import { useDraggable } from '@dnd-kit/core' import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { VARIABLES } from '../engine/variables.js' import { getUnitsForFamily } from '../engine/units.js' import { formatValue } from '../engine/format.js' // Card shown in the left palette — draggable export function PaletteCard({ varId, isInWorkspace }) { const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: `palette-${varId}`, data: { varId, source: 'palette' }, }) const v = VARIABLES[varId] if (!v) return null return (
{v.symbol}
{v.name}
{v.units}
{isInWorkspace && ( )}
) } // Card shown in the workspace export function WorkspaceCard({ varId, userValue, solvedInfo, onRemove, onValueChange, getUnit, onUnitChange, sciNotation, areaRatioBranch, onToggleBranch, }) { const inputRef = useRef(null) const [inputError, setInputError] = useState(false) const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: varId }) const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, } const v = VARIABLES[varId] if (!v) return null const unit = getUnit(varId) const availableUnits = getUnitsForFamily(v.unitFamily) const isUserSet = userValue !== undefined && userValue !== '' const isSolved = !!solvedInfo const handleInputChange = (rawDisplayValue) => { if (rawDisplayValue === '') { setInputError(false) onValueChange(rawDisplayValue) return } const parsed = parseFloat(rawDisplayValue) if (isNaN(parsed)) return // Validate positive constraint if (v.positive && parsed < 0) { setInputError(true) return } setInputError(false) onValueChange(rawDisplayValue) } // State: user-entered (blue), solved (green), unknown (grey), error (red) const borderColor = inputError ? 'border-red-500' : isUserSet ? 'border-blue-500' : isSolved ? 'border-green-500' : 'border-slate-600' const symbolColor = inputError ? 'text-red-300' : isUserSet ? 'text-blue-300' : isSolved ? 'text-green-300' : 'text-slate-400' const valueBg = inputError ? 'bg-red-950' : isUserSet ? 'bg-blue-950' : isSolved ? 'bg-green-950' : 'bg-slate-800' // Display value converted from stored SI to current display unit const inputDisplayValue = isUserSet ? parseFloat(unit.fromSI(userValue).toPrecision(10)) : '' const siUnit = availableUnits[0] const isNonSI = unit.id !== siUnit.id return (
{v.symbol} {availableUnits.length > 1 ? ( ) : ( {unit.label} )} {isSolved && varId === 'Me' && solvedInfo?.equationName === 'Isentropic Area Ratio' && onToggleBranch && ( )}
{v.name}
{isSolved && !isUserSet ? (
{formatValue(unit.fromSI(solvedInfo.value), sciNotation)}
{isNonSI && (
= {formatValue(solvedInfo.value, sciNotation)} {siUnit.label}
)}
via {solvedInfo.equationName}
) : (
handleInputChange(e.target.value)} placeholder="Enter value…" className={`w-full bg-transparent font-mono text-sm ${inputError ? 'text-red-200' : 'text-blue-200'} placeholder-slate-600 outline-none`} /> {inputError && (

Must be positive

)} {isUserSet && isNonSI && !inputError && (
= {formatValue(userValue, sciNotation)} {siUnit.label}
)}
)}
) }