import { useRef, useEffect, useMemo, useCallback, MouseEventHandler, } from 'react' import { DebugPanel } from './components/DebugPanel' import { v4 as uuidv4 } from 'uuid' import { asyncLexer } from './lang/tokeniser' import { abstractSyntaxTree } from './lang/abstractSyntaxTree' import { _executor, ExtrudeGroup, SketchGroup } from './lang/executor' import CodeMirror from '@uiw/react-codemirror' import { linter, lintGutter, Diagnostic } from '@codemirror/lint' import { javascript } from '@codemirror/lang-javascript' import { ViewUpdate } from '@codemirror/view' import { lineHighlightField, addLineHighlight, } from './editor/highlightextension' import { PaneType, Selections, useStore } from './useStore' import { Logs, KCLErrors } from './components/Logs' import { CollapsiblePanel } from './components/CollapsiblePanel' import { MemoryPanel } from './components/MemoryPanel' import { useHotKeyListener } from './hooks/useHotKeyListener' import { Stream } from './components/Stream' import ModalContainer from 'react-modal-promise' import { EngineCommand, EngineCommandManager, } from './lang/std/engineConnection' import { isOverlap, throttle } from './lib/utils' import { AppHeader } from './components/AppHeader' import { KCLError, kclErrToDiagnostic } from './lang/errors' import { Resizable } from 're-resizable' import { faCode, faCodeCommit, faSquareRootVariable, } from '@fortawesome/free-solid-svg-icons' import { useHotkeys } from 'react-hotkeys-hook' export function App() { const cam = useRef() useHotKeyListener() const { editorView, setEditorView, setSelectionRanges, selectionRanges, addLog, addKCLError, code, setCode, setAst, setError, setProgramMemory, resetLogs, resetKCLErrors, selectionRangeTypeMap, setArtifactMap, engineCommandManager: _engineCommandManager, setEngineCommandManager, setHighlightRange, setCursor2, sourceRangeMap, setMediaStream, setIsStreamReady, isStreamReady, isMouseDownInStream, fileId, cmdId, setCmdId, token, formatCode, debugPanel, theme, openPanes, setOpenPanes, onboardingStatus, } = useStore((s) => ({ editorView: s.editorView, setEditorView: s.setEditorView, setSelectionRanges: s.setSelectionRanges, selectionRanges: s.selectionRanges, setGuiMode: s.setGuiMode, addLog: s.addLog, code: s.code, setCode: s.setCode, setAst: s.setAst, setError: s.setError, setProgramMemory: s.setProgramMemory, resetLogs: s.resetLogs, resetKCLErrors: s.resetKCLErrors, selectionRangeTypeMap: s.selectionRangeTypeMap, setArtifactMap: s.setArtifactNSourceRangeMaps, engineCommandManager: s.engineCommandManager, setEngineCommandManager: s.setEngineCommandManager, setHighlightRange: s.setHighlightRange, isShiftDown: s.isShiftDown, setCursor: s.setCursor, setCursor2: s.setCursor2, sourceRangeMap: s.sourceRangeMap, setMediaStream: s.setMediaStream, isStreamReady: s.isStreamReady, setIsStreamReady: s.setIsStreamReady, isMouseDownInStream: s.isMouseDownInStream, fileId: s.fileId, cmdId: s.cmdId, setCmdId: s.setCmdId, token: s.token, formatCode: s.formatCode, debugPanel: s.debugPanel, addKCLError: s.addKCLError, theme: s.theme, openPanes: s.openPanes, setOpenPanes: s.setOpenPanes, onboardingStatus: s.onboardingStatus, })) // Pane toggling keyboard shortcuts const togglePane = useCallback( (newPane: PaneType) => openPanes.includes(newPane) ? setOpenPanes(openPanes.filter((p) => p !== newPane)) : setOpenPanes([...openPanes, newPane]), [openPanes, setOpenPanes] ) useHotkeys('shift + c', () => togglePane('code')) useHotkeys('shift + v', () => togglePane('variables')) useHotkeys('shift + l', () => togglePane('logs')) useHotkeys('shift + e', () => togglePane('kclErrors')) useHotkeys('shift + d', () => togglePane('debug')) const paneOpacity = onboardingStatus === 'camera' ? 'opacity-20' : '' // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { const onChange = (value: string, viewUpdate: ViewUpdate) => { setCode(value) if (editorView) { editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) }) } } //, []); const onUpdate = (viewUpdate: ViewUpdate) => { if (!editorView) { setEditorView(viewUpdate.view) } const ranges = viewUpdate.state.selection.ranges const isChange = ranges.length !== selectionRanges.codeBasedSelections.length || ranges.some(({ from, to }, i) => { return ( from !== selectionRanges.codeBasedSelections[i].range[0] || to !== selectionRanges.codeBasedSelections[i].range[1] ) }) if (!isChange) return const codeBasedSelections: Selections['codeBasedSelections'] = ranges.map( ({ from, to }) => { if (selectionRangeTypeMap[to]) { return { type: selectionRangeTypeMap[to], range: [from, to], } } return { type: 'default', range: [from, to], } } ) const idBasedSelections = codeBasedSelections .map(({ type, range }) => { const hasOverlap = Object.entries(sourceRangeMap).filter( ([_, sourceRange]) => { return isOverlap(sourceRange, range) } ) if (hasOverlap.length) { return { type, id: hasOverlap[0][0], } } }) .filter(Boolean) as any _engineCommandManager?.cusorsSelected({ otherSelections: [], idBasedSelections, }) setSelectionRanges({ otherSelections: [], codeBasedSelections, }) } const engineCommandManager = useMemo(() => { return new EngineCommandManager({ setMediaStream, setIsStreamReady, token, }) }, [token]) useEffect(() => { return () => { engineCommandManager?.tearDown() } }, [engineCommandManager]) useEffect(() => { if (!isStreamReady) return const asyncWrap = async () => { try { if (!code) { setAst(null) return } const tokens = await asyncLexer(code) const _ast = abstractSyntaxTree(tokens) setAst(_ast) resetLogs() resetKCLErrors() if (_engineCommandManager) { _engineCommandManager.endSession() } engineCommandManager.startNewSession() setEngineCommandManager(engineCommandManager) _executor( _ast, { root: { log: { type: 'userVal', value: (a: any) => { addLog(a) }, __meta: [ { pathToNode: [], sourceRange: [0, 0], }, ], }, _0: { type: 'userVal', value: 0, __meta: [], }, _90: { type: 'userVal', value: 90, __meta: [], }, _180: { type: 'userVal', value: 180, __meta: [], }, _270: { type: 'userVal', value: 270, __meta: [], }, }, pendingMemory: {}, }, engineCommandManager, { bodyType: 'root' }, [] ).then(async (programMemory) => { const { artifactMap, sourceRangeMap } = await engineCommandManager.waitForAllCommands() setArtifactMap({ artifactMap, sourceRangeMap }) engineCommandManager.onHover((id) => { if (!id) { setHighlightRange([0, 0]) } else { const sourceRange = sourceRangeMap[id] setHighlightRange(sourceRange) } }) engineCommandManager.onClick(({ id, type }) => { setCursor2({ range: sourceRangeMap[id], type }) }) setProgramMemory(programMemory) const geos = programMemory?.return ?.map(({ name }: { name: string }) => { const artifact = programMemory?.root?.[name] if ( artifact.type === 'extrudeGroup' || artifact.type === 'sketchGroup' ) { return artifact } return null }) .filter((a) => a) as (ExtrudeGroup | SketchGroup)[] // console.log(programMemory) setError() }) } catch (e: any) { if (e instanceof KCLError) { addKCLError(e) } else { setError('problem') console.log(e) addLog(e) } } } asyncWrap() }, [code, isStreamReady]) const debounceSocketSend = throttle((message) => { engineCommandManager?.sendSceneCommand(message) }, 16) const handleMouseMove = useCallback>( ({ clientX, clientY, ctrlKey, currentTarget }) => { if (!cmdId) return if (!isMouseDownInStream) return const { left, top } = currentTarget.getBoundingClientRect() const x = clientX - left const y = clientY - top const interaction = ctrlKey ? 'pan' : 'rotate' const newCmdId = uuidv4() setCmdId(newCmdId) debounceSocketSend({ type: 'modeling_cmd_req', cmd: { type: 'camera_drag_move', interaction, window: { x, y }, }, cmd_id: newCmdId, file_id: fileId, }) }, [debounceSocketSend, isMouseDownInStream, cmdId, fileId, setCmdId] ) return (
{ return kclErrToDiagnostic(useStore.getState().kclErrors) }), ]} onChange={onChange} onUpdate={onUpdate} theme={theme} onCreateEditor={(_editorView) => setEditorView(_editorView)} />
{debugPanel && ( )}
) }