2023-09-14 10:47:55 +10:00
|
|
|
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
2023-07-21 16:53:06 +10:00
|
|
|
import { DebugPanel } from './components/DebugPanel'
|
2023-08-06 21:29:26 -04:00
|
|
|
import { v4 as uuidv4 } from 'uuid'
|
2023-09-09 01:38:36 -04:00
|
|
|
import { PaneType, useStore } from './useStore'
|
2023-07-26 14:10:30 -05:00
|
|
|
import { Logs, KCLErrors } from './components/Logs'
|
2023-08-06 21:29:26 -04:00
|
|
|
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
2023-02-03 10:04:16 +11:00
|
|
|
import { MemoryPanel } from './components/MemoryPanel'
|
2023-02-21 10:28:34 +11:00
|
|
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
2023-03-06 20:13:34 +11:00
|
|
|
import { Stream } from './components/Stream'
|
2023-03-07 15:45:59 +11:00
|
|
|
import ModalContainer from 'react-modal-promise'
|
2023-09-14 10:47:55 +10:00
|
|
|
import { EngineCommand } from './lang/std/engineConnection'
|
2023-09-09 01:38:36 -04:00
|
|
|
import { throttle } from './lib/utils'
|
2023-07-13 07:22:08 -04:00
|
|
|
import { AppHeader } from './components/AppHeader'
|
2023-08-06 21:29:26 -04:00
|
|
|
import { Resizable } from 're-resizable'
|
|
|
|
import {
|
|
|
|
faCode,
|
|
|
|
faCodeCommit,
|
|
|
|
faSquareRootVariable,
|
|
|
|
} from '@fortawesome/free-solid-svg-icons'
|
|
|
|
import { useHotkeys } from 'react-hotkeys-hook'
|
2023-08-09 20:49:10 +10:00
|
|
|
import { getNormalisedCoordinates } from './lib/utils'
|
2023-08-15 21:56:24 -04:00
|
|
|
import { isTauri } from './lib/isTauri'
|
2023-09-09 01:38:36 -04:00
|
|
|
import { useLoaderData } from 'react-router-dom'
|
2023-08-15 21:56:24 -04:00
|
|
|
import { IndexLoaderData } from './Router'
|
2023-08-29 10:48:55 -04:00
|
|
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
2023-08-31 16:08:15 -04:00
|
|
|
import { onboardingPaths } from 'routes/Onboarding'
|
2023-09-08 10:13:35 -04:00
|
|
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
|
|
|
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
2023-09-09 01:38:36 -04:00
|
|
|
import { CodeMenu } from 'components/CodeMenu'
|
|
|
|
import { TextEditor } from 'components/TextEditor'
|
|
|
|
import { Themes, getSystemTheme } from 'lib/theme'
|
2023-09-14 10:47:55 +10:00
|
|
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
2023-09-15 04:35:48 -07:00
|
|
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
2023-09-25 19:49:53 -07:00
|
|
|
import { engineCommandManager } from './lang/std/engineConnection'
|
2022-11-22 09:06:08 +11:00
|
|
|
|
2023-06-22 16:43:33 +10:00
|
|
|
export function App() {
|
2023-08-18 10:27:01 -04:00
|
|
|
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
2023-09-09 01:38:36 -04:00
|
|
|
|
2023-08-09 20:49:10 +10:00
|
|
|
const streamRef = useRef<HTMLDivElement>(null)
|
2023-02-21 10:28:34 +11:00
|
|
|
useHotKeyListener()
|
2022-11-27 14:06:33 +11:00
|
|
|
const {
|
2023-09-20 10:32:36 -04:00
|
|
|
setCode,
|
2023-09-08 10:13:35 -04:00
|
|
|
buttonDownInStream,
|
2023-08-06 21:29:26 -04:00
|
|
|
openPanes,
|
|
|
|
setOpenPanes,
|
2023-08-18 10:27:01 -04:00
|
|
|
didDragInStream,
|
2023-08-09 20:49:10 +10:00
|
|
|
streamDimensions,
|
2023-09-13 08:36:47 +10:00
|
|
|
guiMode,
|
2023-09-15 04:35:48 -07:00
|
|
|
setGuiMode,
|
2023-09-17 21:57:43 -07:00
|
|
|
executeAst,
|
2022-11-27 14:06:33 +11:00
|
|
|
} = useStore((s) => ({
|
2023-09-13 08:36:47 +10:00
|
|
|
guiMode: s.guiMode,
|
2023-09-15 04:35:48 -07:00
|
|
|
setGuiMode: s.setGuiMode,
|
2023-09-20 10:32:36 -04:00
|
|
|
setCode: s.setCode,
|
2023-09-08 10:13:35 -04:00
|
|
|
buttonDownInStream: s.buttonDownInStream,
|
2023-08-06 21:29:26 -04:00
|
|
|
openPanes: s.openPanes,
|
|
|
|
setOpenPanes: s.setOpenPanes,
|
2023-08-18 10:27:01 -04:00
|
|
|
didDragInStream: s.didDragInStream,
|
2023-08-09 20:49:10 +10:00
|
|
|
streamDimensions: s.streamDimensions,
|
2023-09-17 21:57:43 -07:00
|
|
|
executeAst: s.executeAst,
|
2022-11-27 14:06:33 +11:00
|
|
|
}))
|
2023-08-28 20:31:49 -04:00
|
|
|
|
2023-08-29 10:48:55 -04:00
|
|
|
const {
|
|
|
|
auth: {
|
|
|
|
context: { token },
|
|
|
|
},
|
|
|
|
settings: {
|
2023-09-09 01:38:36 -04:00
|
|
|
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
|
2023-08-29 10:48:55 -04:00
|
|
|
},
|
|
|
|
} = useGlobalStateContext()
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-08-09 13:57:07 -04:00
|
|
|
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
|
|
|
|
2023-08-06 21:29:26 -04:00
|
|
|
// 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'))
|
2023-09-15 04:35:48 -07:00
|
|
|
useHotkeys('esc', () => {
|
|
|
|
if (guiMode.mode === 'sketch') {
|
|
|
|
if (guiMode.sketchMode === 'selectFace') return
|
|
|
|
if (guiMode.sketchMode === 'sketchEdit') {
|
2023-09-17 21:57:43 -07:00
|
|
|
// TODO: share this with Toolbar's "Exit sketch" button
|
|
|
|
// exiting sketch should be done consistently across all exits
|
2023-09-25 19:49:53 -07:00
|
|
|
engineCommandManager.sendSceneCommand({
|
2023-09-15 04:35:48 -07:00
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: { type: 'edit_mode_exit' },
|
|
|
|
})
|
2023-09-25 19:49:53 -07:00
|
|
|
engineCommandManager.sendSceneCommand({
|
2023-09-17 21:57:43 -07:00
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
|
|
|
})
|
2023-09-15 04:35:48 -07:00
|
|
|
setGuiMode({ mode: 'default' })
|
2023-09-17 21:57:43 -07:00
|
|
|
// this is necessary to get the UI back into a consistent
|
|
|
|
// state right now, hopefully won't need to rerender
|
|
|
|
// when exiting sketch mode in the future
|
|
|
|
executeAst()
|
2023-09-15 04:35:48 -07:00
|
|
|
} else {
|
2023-09-25 19:49:53 -07:00
|
|
|
engineCommandManager.sendSceneCommand({
|
2023-09-15 04:35:48 -07:00
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'set_tool',
|
|
|
|
tool: 'select',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
setGuiMode({
|
|
|
|
mode: 'sketch',
|
|
|
|
sketchMode: 'sketchEdit',
|
|
|
|
rotation: guiMode.rotation,
|
|
|
|
position: guiMode.position,
|
|
|
|
pathToNode: guiMode.pathToNode,
|
2023-09-17 21:57:43 -07:00
|
|
|
pathId: guiMode.pathId,
|
2023-09-15 04:35:48 -07:00
|
|
|
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
setGuiMode({ mode: 'default' })
|
|
|
|
}
|
|
|
|
})
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-09-15 22:37:40 -04:00
|
|
|
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
|
|
|
(p) => p === onboardingStatus
|
|
|
|
)
|
|
|
|
? 'opacity-20'
|
|
|
|
: didDragInStream
|
|
|
|
? 'opacity-40'
|
|
|
|
: ''
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-08-15 21:56:24 -04:00
|
|
|
// Use file code loaded from disk
|
|
|
|
// on mount, and overwrite any locally-stored code
|
|
|
|
useEffect(() => {
|
|
|
|
if (isTauri() && loadedCode !== null) {
|
2023-09-20 10:32:36 -04:00
|
|
|
setCode(loadedCode)
|
2023-08-15 21:56:24 -04:00
|
|
|
}
|
|
|
|
return () => {
|
|
|
|
// Clear code on unmount if in desktop app
|
|
|
|
if (isTauri()) {
|
2023-09-20 10:32:36 -04:00
|
|
|
setCode('')
|
2023-08-15 21:56:24 -04:00
|
|
|
}
|
|
|
|
}
|
2023-09-20 10:32:36 -04:00
|
|
|
}, [loadedCode, setCode])
|
2023-08-15 21:56:24 -04:00
|
|
|
|
2023-09-14 10:47:55 +10:00
|
|
|
useSetupEngineManager(streamRef, token)
|
2023-09-15 04:35:48 -07:00
|
|
|
useEngineConnectionSubscriptions()
|
2023-07-31 06:33:10 -04:00
|
|
|
|
2023-08-06 21:29:26 -04:00
|
|
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
2023-09-25 19:49:53 -07:00
|
|
|
engineCommandManager.sendSceneCommand(message)
|
2023-08-06 21:29:26 -04:00
|
|
|
}, 16)
|
2023-09-08 10:13:35 -04:00
|
|
|
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
|
|
e.nativeEvent.preventDefault()
|
2023-08-09 20:49:10 +10:00
|
|
|
|
|
|
|
const { x, y } = getNormalisedCoordinates({
|
2023-09-08 10:13:35 -04:00
|
|
|
clientX: e.clientX,
|
|
|
|
clientY: e.clientY,
|
|
|
|
el: e.currentTarget,
|
2023-08-09 20:49:10 +10:00
|
|
|
...streamDimensions,
|
|
|
|
})
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-08-09 20:49:10 +10:00
|
|
|
const newCmdId = uuidv4()
|
2023-09-13 08:36:47 +10:00
|
|
|
if (buttonDownInStream === undefined) {
|
|
|
|
if (
|
|
|
|
guiMode.mode === 'sketch' &&
|
|
|
|
guiMode.sketchMode === ('sketch_line' as any)
|
|
|
|
) {
|
|
|
|
debounceSocketSend({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: newCmdId,
|
|
|
|
cmd: {
|
|
|
|
type: 'mouse_move',
|
|
|
|
window: { x, y },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
debounceSocketSend({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'highlight_set_entity',
|
|
|
|
selected_at_window: { x, y },
|
|
|
|
},
|
|
|
|
cmd_id: newCmdId,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
2023-09-13 17:42:42 +10:00
|
|
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
|
|
|
debounceSocketSend({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: newCmdId,
|
|
|
|
cmd: {
|
|
|
|
type: 'handle_mouse_drag_move',
|
|
|
|
window: { x, y },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2023-09-08 10:13:35 -04:00
|
|
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
|
|
|
let interaction: CameraDragInteractionType_type
|
|
|
|
|
|
|
|
const eWithButton = { ...e, button: buttonDownInStream }
|
|
|
|
|
|
|
|
if (interactionGuards.pan.callback(eWithButton)) {
|
|
|
|
interaction = 'pan'
|
|
|
|
} else if (interactionGuards.rotate.callback(eWithButton)) {
|
|
|
|
interaction = 'rotate'
|
|
|
|
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
|
|
|
|
interaction = 'zoom'
|
|
|
|
} else {
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 16:21:23 -04:00
|
|
|
|
2023-08-06 21:29:26 -04:00
|
|
|
debounceSocketSend({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'camera_drag_move',
|
|
|
|
interaction,
|
|
|
|
window: { x, y },
|
|
|
|
},
|
|
|
|
cmd_id: newCmdId,
|
|
|
|
})
|
2023-08-09 20:49:10 +10:00
|
|
|
}
|
|
|
|
}
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2022-11-12 13:11:54 +11:00
|
|
|
return (
|
2023-08-06 21:29:26 -04:00
|
|
|
<div
|
2023-08-21 10:52:41 -04:00
|
|
|
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
|
2023-08-06 21:29:26 -04:00
|
|
|
onMouseMove={handleMouseMove}
|
2023-08-09 20:49:10 +10:00
|
|
|
ref={streamRef}
|
2023-08-06 21:29:26 -04:00
|
|
|
>
|
|
|
|
<AppHeader
|
|
|
|
className={
|
2023-08-07 17:07:28 -04:00
|
|
|
'transition-opacity transition-duration-75 ' +
|
|
|
|
paneOpacity +
|
2023-09-08 10:13:35 -04:00
|
|
|
(buttonDownInStream ? ' pointer-events-none' : '')
|
2023-08-06 21:29:26 -04:00
|
|
|
}
|
2023-08-18 10:27:01 -04:00
|
|
|
project={project}
|
|
|
|
enableMenu={true}
|
2023-08-06 21:29:26 -04:00
|
|
|
/>
|
2023-03-07 15:45:59 +11:00
|
|
|
<ModalContainer />
|
2023-08-06 21:29:26 -04:00
|
|
|
<Resizable
|
|
|
|
className={
|
2023-08-08 12:39:11 -04:00
|
|
|
'h-full flex flex-col flex-1 z-10 my-5 ml-5 pr-1 transition-opacity transition-duration-75 ' +
|
2023-09-08 10:13:35 -04:00
|
|
|
(buttonDownInStream || onboardingStatus === 'camera'
|
2023-08-06 21:29:26 -04:00
|
|
|
? ' pointer-events-none '
|
|
|
|
: ' ') +
|
|
|
|
paneOpacity
|
|
|
|
}
|
|
|
|
defaultSize={{
|
2023-09-13 08:36:47 +10:00
|
|
|
width: '550px',
|
2023-08-06 21:29:26 -04:00
|
|
|
height: 'auto',
|
|
|
|
}}
|
|
|
|
minWidth={200}
|
2023-09-13 08:36:47 +10:00
|
|
|
maxWidth={800}
|
2023-08-06 21:29:26 -04:00
|
|
|
minHeight={'auto'}
|
|
|
|
maxHeight={'auto'}
|
|
|
|
handleClasses={{
|
|
|
|
right:
|
|
|
|
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
|
|
|
}}
|
|
|
|
>
|
2023-09-15 22:37:40 -04:00
|
|
|
<div id="code-pane" className="h-full flex flex-col justify-between">
|
2023-08-08 12:39:11 -04:00
|
|
|
<CollapsiblePanel
|
|
|
|
title="Code"
|
|
|
|
icon={faCode}
|
2023-09-06 21:27:30 -04:00
|
|
|
className="open:!mb-2"
|
2023-08-08 12:39:11 -04:00
|
|
|
open={openPanes.includes('code')}
|
2023-09-09 01:38:36 -04:00
|
|
|
menu={<CodeMenu />}
|
2023-08-08 12:39:11 -04:00
|
|
|
>
|
2023-09-09 01:38:36 -04:00
|
|
|
<TextEditor theme={editorTheme} />
|
2023-08-08 12:39:11 -04:00
|
|
|
</CollapsiblePanel>
|
|
|
|
<section className="flex flex-col">
|
|
|
|
<MemoryPanel
|
2023-08-09 13:57:07 -04:00
|
|
|
theme={editorTheme}
|
2023-08-08 12:39:11 -04:00
|
|
|
open={openPanes.includes('variables')}
|
|
|
|
title="Variables"
|
|
|
|
icon={faSquareRootVariable}
|
|
|
|
/>
|
|
|
|
<Logs
|
2023-08-09 13:57:07 -04:00
|
|
|
theme={editorTheme}
|
2023-08-08 12:39:11 -04:00
|
|
|
open={openPanes.includes('logs')}
|
|
|
|
title="Logs"
|
|
|
|
icon={faCodeCommit}
|
|
|
|
/>
|
|
|
|
<KCLErrors
|
2023-08-09 13:57:07 -04:00
|
|
|
theme={editorTheme}
|
2023-08-08 12:39:11 -04:00
|
|
|
open={openPanes.includes('kclErrors')}
|
|
|
|
title="KCL Errors"
|
|
|
|
iconClassNames={{ icon: 'group-open:text-destroy-30' }}
|
2023-08-06 21:29:26 -04:00
|
|
|
/>
|
2023-08-08 12:39:11 -04:00
|
|
|
</section>
|
|
|
|
</div>
|
2023-08-06 21:29:26 -04:00
|
|
|
</Resizable>
|
|
|
|
<Stream className="absolute inset-0 z-0" />
|
2023-08-28 20:31:49 -04:00
|
|
|
{showDebugPanel && (
|
2023-08-06 21:29:26 -04:00
|
|
|
<DebugPanel
|
|
|
|
title="Debug"
|
|
|
|
className={
|
2023-08-07 17:07:28 -04:00
|
|
|
'transition-opacity transition-duration-75 ' +
|
|
|
|
paneOpacity +
|
2023-09-08 10:13:35 -04:00
|
|
|
(buttonDownInStream ? ' pointer-events-none' : '')
|
2023-08-06 21:29:26 -04:00
|
|
|
}
|
|
|
|
open={openPanes.includes('debug')}
|
|
|
|
/>
|
|
|
|
)}
|
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
|
|
|
}
|