Files
modeling-app/src/App.tsx

310 lines
8.8 KiB
TypeScript
Raw Normal View History

2022-11-28 19:43:20 +11:00
import { useRef, useState, useEffect, useMemo } from 'react'
2022-11-26 08:34:23 +11:00
import { Canvas } from '@react-three/fiber'
import { Allotment } from 'allotment'
import { OrbitControls, OrthographicCamera } from '@react-three/drei'
import { lexer } from './lang/tokeniser'
2022-12-06 05:40:05 +11:00
import {
abstractSyntaxTree,
getNodePathFromSourceRange,
getNodeFromPath
} from './lang/abstractSyntaxTree'
import { executor, processShownObjects, ViewerArtifact } from './lang/executor'
2022-11-28 19:43:20 +11:00
import { recast } from './lang/recast'
2022-11-26 08:34:23 +11:00
import { BufferGeometry } from 'three'
import CodeMirror from '@uiw/react-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { ViewUpdate } from '@codemirror/view'
import {
lineHighlightField,
addLineHighlight,
2022-11-26 08:34:23 +11:00
} from './editor/highlightextension'
import { useStore } from './useStore'
import { isOverlapping } from './lib/utils'
import { Toolbar } from './Toolbar'
import { BasePlanes } from './components/BasePlanes'
import { SketchPlane } from './components/SketchPlane'
import { Logs } from './components/Logs'
2022-11-28 21:05:56 +11:00
import { AxisIndicator } from './components/AxisIndicator'
2022-11-26 08:34:23 +11:00
const OrrthographicCamera = OrthographicCamera as any
2022-11-12 13:11:54 +11:00
function App() {
2022-11-26 08:34:23 +11:00
const cam = useRef()
const {
editorView,
setEditorView,
setSelectionRange,
selectionRange,
guiMode,
lastGuiMode,
removeError,
addLog,
code,
setCode,
setAst,
2022-11-28 19:43:20 +11:00
formatCode,
ast,
2022-12-06 05:40:05 +11:00
setError,
errorState,
} = useStore((s) => ({
editorView: s.editorView,
setEditorView: s.setEditorView,
setSelectionRange: s.setSelectionRange,
selectionRange: s.selectionRange,
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
removeError: s.removeError,
addLog: s.addLog,
code: s.code,
setCode: s.setCode,
ast: s.ast,
setAst: s.setAst,
2022-11-28 19:43:20 +11:00
lastGuiMode: s.lastGuiMode,
formatCode: s.formatCode,
2022-12-06 05:40:05 +11:00
setError: s.setError,
errorState: s.errorState,
}))
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
2022-11-26 08:34:23 +11:00
setCode(value)
if (editorView) {
2022-11-26 08:34:23 +11:00
editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) })
}
2022-11-26 08:34:23 +11:00
} //, []);
const onUpdate = (viewUpdate: ViewUpdate) => {
if (!editorView) {
2022-11-26 08:34:23 +11:00
setEditorView(viewUpdate.view)
}
2022-11-26 08:34:23 +11:00
const range = viewUpdate.state.selection.ranges[0]
const isNoChange =
range.from === selectionRange[0] && range.to === selectionRange[1]
if (isNoChange) return
2022-11-26 08:34:23 +11:00
setSelectionRange([range.from, range.to])
}
const [geoArray, setGeoArray] = useState<ViewerArtifact[]>([])
useEffect(() => {
try {
if (!code) {
setGeoArray([])
setAst(null)
removeError()
return
}
2022-11-26 08:34:23 +11:00
const tokens = lexer(code)
const _ast = abstractSyntaxTree(tokens)
setAst(_ast)
const programMemory = executor(_ast, {
root: {
log: (a: any) => {
let b = a
if (Array.isArray(a)) {
b = a.map(({ geo, ...rest }) => rest)
b = JSON.stringify(b, null, 2)
} else if (typeof a === 'object') {
b = JSON.stringify(a, null, 2)
}
addLog(b)
},
},
_sketch: [],
})
const geos: ViewerArtifact[] =
programMemory?.return?.flatMap(
({ name }: { name: string }) =>
2022-11-29 19:03:50 +11:00
processShownObjects(programMemory, programMemory?.root?.[name]) ||
[]
) || []
2022-11-26 08:34:23 +11:00
setGeoArray(geos)
removeError()
2022-11-26 08:34:23 +11:00
console.log(programMemory)
2022-12-06 05:40:05 +11:00
setError()
} catch (e: any) {
2022-12-06 05:40:05 +11:00
setError('problem')
2022-11-26 08:34:23 +11:00
console.log(e)
addLog(e)
}
2022-11-26 08:34:23 +11:00
}, [code])
2022-11-28 19:43:20 +11:00
const shouldFormat = useMemo(() => {
2022-11-28 19:44:08 +11:00
if (!ast) return false
2022-11-28 19:43:20 +11:00
const recastedCode = recast(ast)
return recastedCode !== code
}, [code, ast])
2022-11-12 13:11:54 +11:00
return (
<div className="h-screen">
<Allotment>
<Logs />
2022-11-28 19:43:20 +11:00
<div className="h-full flex flex-col items-start">
2022-11-28 19:44:08 +11:00
<button
disabled={!shouldFormat}
onClick={formatCode}
className={`${!shouldFormat && 'text-gray-300'}`}
>
format
</button>
2022-11-28 19:43:20 +11:00
<div className="bg-red h-full w-full overflow-auto">
<CodeMirror
className="h-full"
value={code}
extensions={[javascript({ jsx: true }), lineHighlightField]}
onChange={onChange}
onUpdate={onUpdate}
onCreateEditor={(_editorView) => setEditorView(_editorView)}
/>
</div>
</div>
<div className="h-full">
<Toolbar />
<div className="border h-full border-gray-300 relative">
<div className="absolute inset-0">
<Canvas>
<OrbitControls
enableDamping={false}
enablePan
enableRotate
enableZoom
reverseOrbit={false}
/>
<OrrthographicCamera
ref={cam}
makeDefault
position={[0, 0, 10]}
zoom={40}
rotation={[0, 0, 0]}
/>
<ambientLight />
<pointLight position={[10, 10, 10]} />
{geoArray.map((artifact, index) => (
<RenderViewerArtifacts artifact={artifact} key={index} />
))}
<BasePlanes />
<SketchPlane />
2022-11-28 21:05:56 +11:00
<AxisIndicator />
</Canvas>
</div>
2022-12-06 05:40:05 +11:00
{errorState.isError && (
<div className="absolute inset-0 bg-gray-700/20">
2022-11-28 19:44:08 +11:00
<pre>
{'last first: \n\n' +
JSON.stringify(lastGuiMode, null, 2) +
'\n\n' +
JSON.stringify(guiMode)}
</pre>
</div>
)}
</div>
</div>
</Allotment>
2022-11-12 13:11:54 +11:00
</div>
2022-11-26 08:34:23 +11:00
)
2022-11-12 13:11:54 +11:00
}
2022-11-26 08:34:23 +11:00
export default App
function Line({
geo,
sourceRange,
forceHighlight = false,
}: {
2022-11-26 08:34:23 +11:00
geo: BufferGeometry
sourceRange: [number, number]
forceHighlight?: boolean
}) {
2022-12-06 05:40:05 +11:00
const { setHighlightRange, selectionRange, guiMode, setGuiMode, ast } =
useStore(
({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({
setHighlightRange,
selectionRange,
guiMode,
setGuiMode,
ast,
})
)
// This reference will give us direct access to the mesh
2022-11-26 08:34:23 +11:00
const ref = useRef<BufferGeometry | undefined>() as any
const [hovered, setHover] = useState(false)
const [editorCursor, setEditorCursor] = useState(false)
2022-12-06 05:40:05 +11:00
const [didSetCanEdit, setDidSetCanEdit] = useState(false)
useEffect(() => {
2022-11-26 08:34:23 +11:00
const shouldHighlight = isOverlapping(sourceRange, selectionRange)
setEditorCursor(shouldHighlight)
2022-12-06 05:40:05 +11:00
if (shouldHighlight && guiMode.mode === 'default' && ast) {
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
const piper = getNodeFromPath(ast, pathToNode, 'PipeExpression')
const axis = piper.type !== 'PipeExpression' ? 'xy'
: piper?.body?.[1]?.callee?.name === 'rx' ? 'xz' : 'yz'
setGuiMode({ mode: 'canEditSketch', pathToNode, axis })
setDidSetCanEdit(true)
} else if (
!shouldHighlight &&
didSetCanEdit &&
guiMode.mode === 'canEditSketch'
) {
setGuiMode({ mode: 'default' })
setDidSetCanEdit(false)
}
2022-11-26 08:34:23 +11:00
}, [selectionRange, sourceRange])
return (
<mesh
ref={ref}
onPointerOver={(event) => {
2022-11-26 08:34:23 +11:00
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
2022-11-26 08:34:23 +11:00
setHover(false)
setHighlightRange([0, 0])
}}
>
<primitive object={geo} />
<meshStandardMaterial
color={
hovered
? 'hotpink'
: editorCursor || forceHighlight
? 'skyblue'
: 'orange'
}
/>
</mesh>
2022-11-26 08:34:23 +11:00
)
}
function RenderViewerArtifacts({
artifact,
forceHighlight = false,
}: {
artifact: ViewerArtifact
forceHighlight?: boolean
}) {
const { selectionRange } = useStore(({ selectionRange }) => ({
selectionRange,
}))
const [editorCursor, setEditorCursor] = useState(false)
useEffect(() => {
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
setEditorCursor(shouldHighlight)
}, [selectionRange, artifact.sourceRange])
2022-12-06 05:40:05 +11:00
if (artifact.type === 'sketchLine') {
const { geo, sourceRange } = artifact
return (
<Line
geo={geo}
sourceRange={sourceRange}
2022-11-29 19:03:50 +11:00
forceHighlight={forceHighlight || editorCursor}
/>
)
}
return (
<>
{artifact.children.map((artifact, index) => (
<RenderViewerArtifacts
artifact={artifact}
key={index}
2022-11-29 19:03:50 +11:00
forceHighlight={forceHighlight || editorCursor}
/>
))}
</>
)
}