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'
|
2022-11-29 11:24:04 +11:00
|
|
|
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'
|
2022-11-26 05:13:07 +11:00
|
|
|
import {
|
|
|
|
lineHighlightField,
|
|
|
|
addLineHighlight,
|
2022-11-26 08:34:23 +11:00
|
|
|
} from './editor/highlightextension'
|
|
|
|
import { useStore } from './useStore'
|
|
|
|
import { isOverlapping } from './lib/utils'
|
2022-11-27 14:06:33 +11:00
|
|
|
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-22 09:06:08 +11:00
|
|
|
|
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()
|
2022-11-27 14:06:33 +11:00
|
|
|
const {
|
|
|
|
editorView,
|
|
|
|
setEditorView,
|
|
|
|
setSelectionRange,
|
|
|
|
selectionRange,
|
|
|
|
guiMode,
|
2022-11-28 09:37:46 +11:00
|
|
|
lastGuiMode,
|
2022-11-27 14:06:33 +11:00
|
|
|
removeError,
|
|
|
|
addLog,
|
2022-11-28 09:37:46 +11:00
|
|
|
code,
|
|
|
|
setCode,
|
|
|
|
setAst,
|
2022-11-28 19:43:20 +11:00
|
|
|
formatCode,
|
|
|
|
ast,
|
2022-12-06 05:40:05 +11:00
|
|
|
setError,
|
|
|
|
errorState,
|
2022-11-27 14:06:33 +11:00
|
|
|
} = 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,
|
2022-11-28 09:37:46 +11:00
|
|
|
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,
|
2022-11-27 14:06:33 +11:00
|
|
|
}))
|
2022-11-26 05:13:07 +11:00
|
|
|
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
|
|
|
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
2022-11-26 08:34:23 +11:00
|
|
|
setCode(value)
|
2022-11-26 05:13:07 +11:00
|
|
|
if (editorView) {
|
2022-11-26 08:34:23 +11:00
|
|
|
editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) })
|
2022-11-26 05:13:07 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
} //, []);
|
2022-11-26 05:13:07 +11:00
|
|
|
const onUpdate = (viewUpdate: ViewUpdate) => {
|
|
|
|
if (!editorView) {
|
2022-11-26 08:34:23 +11:00
|
|
|
setEditorView(viewUpdate.view)
|
2022-11-26 05:13:07 +11:00
|
|
|
}
|
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]
|
2022-11-26 05:13:07 +11:00
|
|
|
if (isNoChange) return
|
2022-11-26 08:34:23 +11:00
|
|
|
setSelectionRange([range.from, range.to])
|
|
|
|
}
|
2022-11-29 11:24:04 +11:00
|
|
|
const [geoArray, setGeoArray] = useState<ViewerArtifact[]>([])
|
2022-11-23 21:28:38 +11:00
|
|
|
useEffect(() => {
|
|
|
|
try {
|
2022-11-27 14:06:33 +11:00
|
|
|
if (!code) {
|
|
|
|
setGeoArray([])
|
2022-11-28 09:37:46 +11:00
|
|
|
setAst(null)
|
2022-11-27 14:06:33 +11:00
|
|
|
removeError()
|
|
|
|
return
|
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
const tokens = lexer(code)
|
2022-11-28 09:37:46 +11:00
|
|
|
const _ast = abstractSyntaxTree(tokens)
|
|
|
|
setAst(_ast)
|
|
|
|
const programMemory = executor(_ast, {
|
2022-11-27 14:06:33 +11:00
|
|
|
root: {
|
|
|
|
log: (a: any) => {
|
2022-11-29 11:24:04 +11:00
|
|
|
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)
|
2022-11-27 14:06:33 +11:00
|
|
|
},
|
|
|
|
},
|
|
|
|
_sketch: [],
|
|
|
|
})
|
2022-11-29 11:24:04 +11:00
|
|
|
const geos: ViewerArtifact[] =
|
2022-11-28 09:37:46 +11:00
|
|
|
programMemory?.return?.flatMap(
|
|
|
|
({ name }: { name: string }) =>
|
2022-11-29 19:03:50 +11:00
|
|
|
processShownObjects(programMemory, programMemory?.root?.[name]) ||
|
2022-11-29 11:24:04 +11:00
|
|
|
[]
|
2022-11-28 09:37:46 +11:00
|
|
|
) || []
|
2022-11-26 08:34:23 +11:00
|
|
|
setGeoArray(geos)
|
2022-11-27 14:06:33 +11:00
|
|
|
removeError()
|
2022-11-26 08:34:23 +11:00
|
|
|
console.log(programMemory)
|
2022-12-06 05:40:05 +11:00
|
|
|
setError()
|
2022-11-27 14:06:33 +11:00
|
|
|
} catch (e: any) {
|
2022-12-06 05:40:05 +11:00
|
|
|
setError('problem')
|
2022-11-26 08:34:23 +11:00
|
|
|
console.log(e)
|
2022-11-27 14:06:33 +11:00
|
|
|
addLog(e)
|
2022-11-23 21:28:38 +11:00
|
|
|
}
|
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 (
|
2022-11-22 09:06:08 +11:00
|
|
|
<div className="h-screen">
|
|
|
|
<Allotment>
|
2022-11-27 14:06:33 +11:00
|
|
|
<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>
|
2022-11-22 09:06:08 +11:00
|
|
|
</div>
|
|
|
|
<div className="h-full">
|
2022-11-27 14:06:33 +11:00
|
|
|
<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]} />
|
2022-11-29 11:24:04 +11:00
|
|
|
{geoArray.map((artifact, index) => (
|
|
|
|
<RenderViewerArtifacts artifact={artifact} key={index} />
|
|
|
|
))}
|
2022-11-27 14:06:33 +11:00
|
|
|
<BasePlanes />
|
|
|
|
<SketchPlane />
|
2022-11-28 21:05:56 +11:00
|
|
|
<AxisIndicator />
|
2022-11-27 14:06:33 +11:00
|
|
|
</Canvas>
|
|
|
|
</div>
|
2022-12-06 05:40:05 +11:00
|
|
|
{errorState.isError && (
|
2022-11-28 09:37:46 +11:00
|
|
|
<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>
|
2022-11-27 14:06:33 +11:00
|
|
|
)}
|
2022-11-26 05:13:07 +11:00
|
|
|
</div>
|
2022-11-22 09:06:08 +11:00
|
|
|
</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
|
2022-11-23 21:28:38 +11:00
|
|
|
|
|
|
|
function Line({
|
|
|
|
geo,
|
|
|
|
sourceRange,
|
2022-11-29 11:24:04 +11:00
|
|
|
forceHighlight = false,
|
2022-11-23 21:28:38 +11:00
|
|
|
}: {
|
2022-11-26 08:34:23 +11:00
|
|
|
geo: BufferGeometry
|
|
|
|
sourceRange: [number, number]
|
2022-11-29 11:24:04 +11:00
|
|
|
forceHighlight?: boolean
|
2022-11-23 21:28:38 +11:00
|
|
|
}) {
|
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,
|
|
|
|
})
|
|
|
|
)
|
2022-11-23 21:28:38 +11:00
|
|
|
// 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)
|
2022-11-26 05:13:07 +11:00
|
|
|
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])
|
2022-11-23 21:28:38 +11:00
|
|
|
|
|
|
|
return (
|
|
|
|
<mesh
|
|
|
|
ref={ref}
|
2022-11-26 05:13:07 +11:00
|
|
|
onPointerOver={(event) => {
|
2022-11-26 08:34:23 +11:00
|
|
|
setHover(true)
|
|
|
|
setHighlightRange(sourceRange)
|
2022-11-26 05:13:07 +11:00
|
|
|
}}
|
|
|
|
onPointerOut={(event) => {
|
2022-11-26 08:34:23 +11:00
|
|
|
setHover(false)
|
|
|
|
setHighlightRange([0, 0])
|
2022-11-26 05:13:07 +11:00
|
|
|
}}
|
2022-11-23 21:28:38 +11:00
|
|
|
>
|
|
|
|
<primitive object={geo} />
|
2022-11-26 05:13:07 +11:00
|
|
|
<meshStandardMaterial
|
2022-11-29 11:24:04 +11:00
|
|
|
color={
|
|
|
|
hovered
|
|
|
|
? 'hotpink'
|
|
|
|
: editorCursor || forceHighlight
|
|
|
|
? 'skyblue'
|
|
|
|
: 'orange'
|
|
|
|
}
|
2022-11-26 05:13:07 +11:00
|
|
|
/>
|
2022-11-23 21:28:38 +11:00
|
|
|
</mesh>
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-23 21:28:38 +11:00
|
|
|
}
|
2022-11-29 11:24:04 +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') {
|
2022-11-29 11:24:04 +11:00
|
|
|
const { geo, sourceRange } = artifact
|
|
|
|
return (
|
|
|
|
<Line
|
|
|
|
geo={geo}
|
|
|
|
sourceRange={sourceRange}
|
2022-11-29 19:03:50 +11:00
|
|
|
forceHighlight={forceHighlight || editorCursor}
|
2022-11-29 11:24:04 +11:00
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{artifact.children.map((artifact, index) => (
|
|
|
|
<RenderViewerArtifacts
|
|
|
|
artifact={artifact}
|
|
|
|
key={index}
|
2022-11-29 19:03:50 +11:00
|
|
|
forceHighlight={forceHighlight || editorCursor}
|
2022-11-29 11:24:04 +11:00
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|