188 lines
6.3 KiB
JavaScript
188 lines
6.3 KiB
JavaScript
import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
|
|
import { useState } from 'react'
|
|
import { VariablePalette } from '../components/VariablePalette.jsx'
|
|
import { Workspace } from '../components/Workspace.jsx'
|
|
import { ResultsPanel } from '../components/ResultsPanel.jsx'
|
|
import { PaletteCard } from '../components/VariableCard.jsx'
|
|
import { PropellantModal } from '../components/PropellantModal.jsx'
|
|
import { EquationBrowser } from '../components/EquationBrowser.jsx'
|
|
import { useSolver } from '../hooks/useSolver.js'
|
|
import { EQUATIONS } from '../engine/equations.js'
|
|
import { buildExportData, exportJSON, parseImport, downloadBlob } from '../engine/exportImport.js'
|
|
import { generateOdt } from '../engine/exportOdt.js'
|
|
|
|
export default function Solver() {
|
|
const {
|
|
workspaceVarIds,
|
|
userValues,
|
|
solved,
|
|
missingReport,
|
|
allKnown,
|
|
unitSelections,
|
|
getUnit,
|
|
setUnit,
|
|
sciNotation,
|
|
areaRatioBranch,
|
|
toggleSciNotation,
|
|
toggleAreaRatioBranch,
|
|
addVariable,
|
|
addVariables,
|
|
removeVariable,
|
|
reorderVariable,
|
|
setValue,
|
|
addPreset,
|
|
applyPropellant,
|
|
clearWorkspace,
|
|
importWorkspace,
|
|
} = useSolver()
|
|
|
|
const [activeVarId, setActiveVarId] = useState(null)
|
|
const [showPropellants, setShowPropellants] = useState(false)
|
|
const [showEquations, setShowEquations] = useState(false)
|
|
|
|
const sensors = useSensors(
|
|
useSensor(PointerSensor, { activationConstraint: { distance: 6 } })
|
|
)
|
|
|
|
function handleDragStart(event) {
|
|
const { varId } = event.active.data.current ?? {}
|
|
if (varId) setActiveVarId(varId)
|
|
}
|
|
|
|
function handleDragEnd(event) {
|
|
const { over, active } = event
|
|
if (!over) {
|
|
setActiveVarId(null)
|
|
return
|
|
}
|
|
|
|
// Palette → workspace drop
|
|
if (over.id === 'workspace') {
|
|
const { varId } = active.data.current ?? {}
|
|
if (varId) addVariable(varId)
|
|
}
|
|
// Workspace → workspace reorder
|
|
else if (workspaceVarIds.includes(active.id) && workspaceVarIds.includes(over.id)) {
|
|
reorderVariable(active.id, over.id)
|
|
}
|
|
setActiveVarId(null)
|
|
}
|
|
|
|
async function handleExportOdt() {
|
|
const data = buildExportData(workspaceVarIds, userValues, solved, unitSelections, getUnit, sciNotation)
|
|
const blob = await generateOdt(data)
|
|
downloadBlob(blob, 'rocketry-workspace.odt')
|
|
}
|
|
|
|
function handleExportJSON() {
|
|
const data = buildExportData(workspaceVarIds, userValues, solved, unitSelections, getUnit, sciNotation)
|
|
const blob = exportJSON(data, workspaceVarIds, userValues, unitSelections)
|
|
downloadBlob(blob, 'rocketry-workspace.json')
|
|
}
|
|
|
|
function handleImportJSON(jsonString) {
|
|
try {
|
|
const workspace = parseImport(jsonString)
|
|
importWorkspace(workspace)
|
|
} catch (err) {
|
|
alert(`Import failed: ${err.message}`)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<DndContext sensors={sensors} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
|
<div className="flex flex-1 min-h-0 overflow-hidden flex-col">
|
|
{/* Solver sub-header */}
|
|
<div className="flex items-center gap-2 px-5 py-2 border-b border-slate-700 bg-slate-900 shrink-0">
|
|
<p className="text-xs text-slate-400">
|
|
Add variables, enter known values, and unknowns solve automatically.
|
|
</p>
|
|
<div className="ml-auto flex items-center gap-2">
|
|
<button
|
|
onClick={toggleSciNotation}
|
|
title="Toggle scientific notation"
|
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg border text-sm font-medium transition-colors ${
|
|
sciNotation
|
|
? 'bg-blue-700 border-blue-500 text-white'
|
|
: 'bg-slate-700 border-slate-600 text-slate-300 hover:bg-slate-600 hover:border-slate-500'
|
|
}`}
|
|
>
|
|
10ˣ
|
|
</button>
|
|
<button
|
|
onClick={() => setShowEquations(true)}
|
|
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 border border-slate-600 hover:border-slate-500 text-sm text-slate-200 font-medium transition-colors"
|
|
>
|
|
<span>∑</span> Equations
|
|
</button>
|
|
<button
|
|
onClick={() => setShowPropellants(true)}
|
|
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 border border-slate-600 hover:border-slate-500 text-sm text-slate-200 font-medium transition-colors"
|
|
>
|
|
<span>⛽</span> Propellants
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Three-panel layout */}
|
|
<div className="flex flex-1 min-h-0 overflow-hidden">
|
|
<VariablePalette
|
|
workspaceVarIds={workspaceVarIds}
|
|
onAddVariable={addVariable}
|
|
/>
|
|
<Workspace
|
|
workspaceVarIds={workspaceVarIds}
|
|
userValues={userValues}
|
|
solved={solved}
|
|
onRemove={removeVariable}
|
|
onValueChange={setValue}
|
|
onAddPreset={addPreset}
|
|
onClear={clearWorkspace}
|
|
getUnit={getUnit}
|
|
setUnit={setUnit}
|
|
sciNotation={sciNotation}
|
|
areaRatioBranch={areaRatioBranch}
|
|
onToggleAreaRatioBranch={toggleAreaRatioBranch}
|
|
/>
|
|
<ResultsPanel
|
|
workspaceVarIds={workspaceVarIds}
|
|
userValues={userValues}
|
|
solved={solved}
|
|
missingReport={missingReport}
|
|
getUnit={getUnit}
|
|
sciNotation={sciNotation}
|
|
onExportOdt={handleExportOdt}
|
|
onExportJSON={handleExportJSON}
|
|
onImportJSON={handleImportJSON}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{showPropellants && (
|
|
<PropellantModal
|
|
onClose={() => setShowPropellants(false)}
|
|
onApply={applyPropellant}
|
|
/>
|
|
)}
|
|
|
|
{showEquations && (
|
|
<EquationBrowser
|
|
equations={EQUATIONS}
|
|
allKnown={allKnown}
|
|
workspaceVarIds={workspaceVarIds}
|
|
onAddVariables={varIds => addVariables(varIds)}
|
|
onClose={() => setShowEquations(false)}
|
|
/>
|
|
)}
|
|
|
|
<DragOverlay dropAnimation={null}>
|
|
{activeVarId ? (
|
|
<div className="opacity-90 rotate-2 scale-105">
|
|
<PaletteCard varId={activeVarId} isInWorkspace={false} />
|
|
</div>
|
|
) : null}
|
|
</DragOverlay>
|
|
</DndContext>
|
|
)
|
|
}
|