2023-08-08 18:30:26 -04:00
|
|
|
import {
|
|
|
|
MouseEventHandler,
|
|
|
|
WheelEventHandler,
|
|
|
|
useEffect,
|
|
|
|
useRef,
|
|
|
|
useState,
|
|
|
|
} from 'react'
|
2023-06-21 09:15:02 +10:00
|
|
|
import { v4 as uuidv4 } from 'uuid'
|
2023-06-22 16:43:33 +10:00
|
|
|
import { useStore } from '../useStore'
|
2023-09-13 08:36:47 +10:00
|
|
|
import { getNormalisedCoordinates, roundOff } from '../lib/utils'
|
2023-08-10 16:22:45 -04:00
|
|
|
import Loading from './Loading'
|
2023-09-08 10:13:35 -04:00
|
|
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
|
|
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
|
|
|
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
2023-09-13 08:36:47 +10:00
|
|
|
import { Models } from '@kittycad/lib'
|
|
|
|
import { addStartSketch } from 'lang/modifyAst'
|
|
|
|
import { addNewSketchLn } from 'lang/std/sketch'
|
2023-03-06 20:13:34 +11:00
|
|
|
|
2023-08-06 21:29:26 -04:00
|
|
|
export const Stream = ({ className = '' }) => {
|
2023-08-10 16:22:45 -04:00
|
|
|
const [isLoading, setIsLoading] = useState(true)
|
2023-09-06 01:32:53 -04:00
|
|
|
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
2023-03-06 20:13:34 +11:00
|
|
|
const videoRef = useRef<HTMLVideoElement>(null)
|
2023-08-06 21:29:26 -04:00
|
|
|
const {
|
|
|
|
mediaStream,
|
|
|
|
engineCommandManager,
|
2023-09-08 10:13:35 -04:00
|
|
|
setButtonDownInStream,
|
2023-08-09 20:49:10 +10:00
|
|
|
didDragInStream,
|
|
|
|
setDidDragInStream,
|
|
|
|
streamDimensions,
|
2023-09-08 17:50:37 +10:00
|
|
|
isExecuting,
|
2023-09-13 08:36:47 +10:00
|
|
|
guiMode,
|
|
|
|
ast,
|
|
|
|
updateAst,
|
|
|
|
setGuiMode,
|
|
|
|
programMemory,
|
2023-08-06 21:29:26 -04:00
|
|
|
} = useStore((s) => ({
|
2023-06-22 16:43:33 +10:00
|
|
|
mediaStream: s.mediaStream,
|
|
|
|
engineCommandManager: s.engineCommandManager,
|
2023-09-08 10:13:35 -04:00
|
|
|
setButtonDownInStream: s.setButtonDownInStream,
|
2023-08-06 21:29:26 -04:00
|
|
|
fileId: s.fileId,
|
2023-08-09 20:49:10 +10:00
|
|
|
didDragInStream: s.didDragInStream,
|
|
|
|
setDidDragInStream: s.setDidDragInStream,
|
|
|
|
streamDimensions: s.streamDimensions,
|
2023-09-08 17:50:37 +10:00
|
|
|
isExecuting: s.isExecuting,
|
2023-09-13 08:36:47 +10:00
|
|
|
guiMode: s.guiMode,
|
|
|
|
ast: s.ast,
|
|
|
|
updateAst: s.updateAst,
|
|
|
|
setGuiMode: s.setGuiMode,
|
|
|
|
programMemory: s.programMemory,
|
2023-06-22 16:43:33 +10:00
|
|
|
}))
|
2023-09-08 10:13:35 -04:00
|
|
|
const {
|
|
|
|
settings: {
|
|
|
|
context: { cameraControls },
|
|
|
|
},
|
|
|
|
} = useGlobalStateContext()
|
2023-03-06 20:13:34 +11:00
|
|
|
|
|
|
|
useEffect(() => {
|
2023-03-07 15:45:59 +11:00
|
|
|
if (
|
|
|
|
typeof window === 'undefined' ||
|
|
|
|
typeof RTCPeerConnection === 'undefined'
|
|
|
|
)
|
|
|
|
return
|
2023-06-22 16:43:33 +10:00
|
|
|
if (!videoRef.current) return
|
|
|
|
if (!mediaStream) return
|
|
|
|
videoRef.current.srcObject = mediaStream
|
2023-08-18 17:14:35 -05:00
|
|
|
}, [mediaStream, engineCommandManager])
|
2023-03-06 20:13:34 +11:00
|
|
|
|
2023-09-08 10:13:35 -04:00
|
|
|
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
|
2023-06-22 16:43:33 +10:00
|
|
|
if (!videoRef.current) return
|
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,
|
2023-08-09 20:49:10 +10:00
|
|
|
el: videoRef.current,
|
|
|
|
...streamDimensions,
|
|
|
|
})
|
2023-06-22 16:43:33 +10:00
|
|
|
|
|
|
|
const newId = uuidv4()
|
|
|
|
|
2023-09-08 10:13:35 -04:00
|
|
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
2023-09-13 08:36:47 +10:00
|
|
|
let interaction: CameraDragInteractionType_type = 'rotate'
|
2023-09-08 10:13:35 -04:00
|
|
|
|
2023-09-11 16:21:23 -04:00
|
|
|
if (
|
|
|
|
interactionGuards.pan.callback(e) ||
|
|
|
|
interactionGuards.pan.lenientDragStartButton === e.button
|
|
|
|
) {
|
2023-09-08 10:13:35 -04:00
|
|
|
interaction = 'pan'
|
2023-09-11 16:21:23 -04:00
|
|
|
} else if (
|
|
|
|
interactionGuards.rotate.callback(e) ||
|
|
|
|
interactionGuards.rotate.lenientDragStartButton === e.button
|
|
|
|
) {
|
2023-09-08 10:13:35 -04:00
|
|
|
interaction = 'rotate'
|
2023-09-11 16:21:23 -04:00
|
|
|
} else if (
|
|
|
|
interactionGuards.zoom.dragCallback(e) ||
|
|
|
|
interactionGuards.zoom.lenientDragStartButton === e.button
|
|
|
|
) {
|
2023-09-08 10:13:35 -04:00
|
|
|
interaction = 'zoom'
|
|
|
|
}
|
2023-07-20 14:08:32 -04:00
|
|
|
|
2023-09-13 08:36:47 +10:00
|
|
|
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
|
|
|
engineCommandManager?.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'handle_mouse_drag_start',
|
|
|
|
window: { x, y },
|
|
|
|
},
|
|
|
|
cmd_id: newId,
|
|
|
|
})
|
|
|
|
} else if (
|
|
|
|
!(
|
|
|
|
guiMode.mode === 'sketch' &&
|
|
|
|
guiMode.sketchMode === ('sketch_line' as any)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
engineCommandManager?.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'camera_drag_start',
|
|
|
|
interaction,
|
|
|
|
window: { x, y },
|
|
|
|
},
|
|
|
|
cmd_id: newId,
|
|
|
|
})
|
|
|
|
}
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-09-08 10:13:35 -04:00
|
|
|
setButtonDownInStream(e.button)
|
2023-09-06 01:32:53 -04:00
|
|
|
setClickCoords({ x, y })
|
2023-06-22 16:43:33 +10:00
|
|
|
}
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-08-08 18:30:26 -04:00
|
|
|
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
|
2023-09-08 10:13:35 -04:00
|
|
|
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
|
|
|
|
2023-08-21 10:52:41 -04:00
|
|
|
engineCommandManager?.sendSceneCommand({
|
2023-08-08 18:30:26 -04:00
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
2023-08-21 10:52:41 -04:00
|
|
|
type: 'default_camera_zoom',
|
|
|
|
magnitude: e.deltaY * 0.4,
|
2023-08-08 18:30:26 -04:00
|
|
|
},
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-06-22 16:43:33 +10:00
|
|
|
const handleMouseUp: MouseEventHandler<HTMLVideoElement> = ({
|
|
|
|
clientX,
|
|
|
|
clientY,
|
2023-07-20 14:08:32 -04:00
|
|
|
ctrlKey,
|
2023-06-22 16:43:33 +10:00
|
|
|
}) => {
|
|
|
|
if (!videoRef.current) return
|
2023-09-13 08:36:47 +10:00
|
|
|
setButtonDownInStream(undefined)
|
2023-08-09 20:49:10 +10:00
|
|
|
const { x, y } = getNormalisedCoordinates({
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
el: videoRef.current,
|
|
|
|
...streamDimensions,
|
|
|
|
})
|
2023-06-22 16:43:33 +10:00
|
|
|
|
2023-08-06 21:29:26 -04:00
|
|
|
const newCmdId = uuidv4()
|
2023-07-20 14:08:32 -04:00
|
|
|
const interaction = ctrlKey ? 'pan' : 'rotate'
|
|
|
|
|
2023-09-13 08:36:47 +10:00
|
|
|
const command: Models['WebSocketRequest_type'] = {
|
2023-08-02 15:41:59 +10:00
|
|
|
type: 'modeling_cmd_req',
|
2023-06-22 16:43:33 +10:00
|
|
|
cmd: {
|
2023-08-02 15:41:59 +10:00
|
|
|
type: 'camera_drag_end',
|
|
|
|
interaction,
|
2023-08-03 07:28:06 +10:00
|
|
|
window: { x, y },
|
2023-06-22 16:43:33 +10:00
|
|
|
},
|
2023-08-06 21:29:26 -04:00
|
|
|
cmd_id: newCmdId,
|
2023-09-13 08:36:47 +10:00
|
|
|
}
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-08-09 20:49:10 +10:00
|
|
|
if (!didDragInStream) {
|
|
|
|
engineCommandManager?.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'select_with_point',
|
|
|
|
selection_type: 'add',
|
|
|
|
selected_at_window: { x, y },
|
|
|
|
},
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
})
|
|
|
|
}
|
2023-09-13 08:36:47 +10:00
|
|
|
|
|
|
|
if (!didDragInStream && guiMode.mode === 'default') {
|
|
|
|
command.cmd = {
|
|
|
|
type: 'select_with_point',
|
|
|
|
selection_type: 'add',
|
|
|
|
selected_at_window: { x, y },
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
(!didDragInStream &&
|
|
|
|
guiMode.mode === 'sketch' &&
|
|
|
|
['move', 'select'].includes(guiMode.sketchMode)) ||
|
|
|
|
(guiMode.mode === 'sketch' &&
|
|
|
|
guiMode.sketchMode === ('sketch_line' as any))
|
|
|
|
) {
|
|
|
|
command.cmd = {
|
|
|
|
type: 'mouse_click',
|
|
|
|
window: { x, y },
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
guiMode.mode === 'sketch' &&
|
|
|
|
guiMode.sketchMode === ('move' as any)
|
|
|
|
) {
|
|
|
|
command.cmd = {
|
|
|
|
type: 'handle_mouse_drag_end',
|
|
|
|
window: { x, y },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
|
|
|
|
if (command.cmd.type !== 'mouse_click' || !ast) return
|
|
|
|
if (
|
|
|
|
!(
|
|
|
|
guiMode.mode === 'sketch' &&
|
|
|
|
guiMode.sketchMode === ('sketch_line' as any as 'line')
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
|
|
|
|
const curve = await engineCommandManager?.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'curve_get_control_points',
|
|
|
|
curve_id: data?.data?.entities_modified[0],
|
|
|
|
},
|
|
|
|
})
|
|
|
|
const coords: { x: number; y: number }[] =
|
|
|
|
curve.data.data.control_points
|
|
|
|
const _addStartSketch = addStartSketch(
|
|
|
|
ast,
|
|
|
|
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
|
|
|
[
|
|
|
|
roundOff(coords[1].x - coords[0].x),
|
|
|
|
roundOff(coords[1].y - coords[0].y),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
const _modifiedAst = _addStartSketch.modifiedAst
|
|
|
|
const _pathToNode = _addStartSketch.pathToNode
|
|
|
|
|
|
|
|
setGuiMode({
|
|
|
|
...guiMode,
|
|
|
|
pathToNode: _pathToNode,
|
|
|
|
waitingFirstClick: false,
|
|
|
|
})
|
|
|
|
updateAst(_modifiedAst)
|
|
|
|
} else if (
|
|
|
|
data?.data?.entities_modified?.length &&
|
|
|
|
!guiMode.waitingFirstClick
|
|
|
|
) {
|
|
|
|
const curve = await engineCommandManager?.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'curve_get_control_points',
|
|
|
|
curve_id: data?.data?.entities_modified[0],
|
|
|
|
},
|
|
|
|
})
|
|
|
|
const coords: { x: number; y: number }[] =
|
|
|
|
curve.data.data.control_points
|
|
|
|
const _modifiedAst = addNewSketchLn({
|
|
|
|
node: ast,
|
|
|
|
programMemory,
|
|
|
|
to: [coords[1].x, coords[1].y],
|
|
|
|
fnName: 'line',
|
|
|
|
pathToNode: guiMode.pathToNode,
|
|
|
|
}).modifiedAst
|
|
|
|
updateAst(_modifiedAst)
|
|
|
|
}
|
|
|
|
})
|
2023-08-09 20:49:10 +10:00
|
|
|
setDidDragInStream(false)
|
2023-09-06 01:32:53 -04:00
|
|
|
setClickCoords(undefined)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleMouseMove: MouseEventHandler<HTMLVideoElement> = (e) => {
|
|
|
|
if (!clickCoords) return
|
|
|
|
|
|
|
|
const delta =
|
|
|
|
((clickCoords.x - e.clientX) ** 2 + (clickCoords.y - e.clientY) ** 2) **
|
|
|
|
0.5
|
|
|
|
|
|
|
|
if (delta > 5 && !didDragInStream) {
|
|
|
|
setDidDragInStream(true)
|
|
|
|
}
|
2023-06-22 16:43:33 +10:00
|
|
|
}
|
2023-03-06 20:13:34 +11:00
|
|
|
|
|
|
|
return (
|
2023-08-06 21:29:26 -04:00
|
|
|
<div id="stream" className={className}>
|
2023-06-22 16:43:33 +10:00
|
|
|
<video
|
|
|
|
ref={videoRef}
|
|
|
|
muted
|
|
|
|
autoPlay
|
|
|
|
controls={false}
|
|
|
|
onMouseDown={handleMouseDown}
|
|
|
|
onMouseUp={handleMouseUp}
|
2023-07-20 14:08:32 -04:00
|
|
|
onContextMenu={(e) => e.preventDefault()}
|
|
|
|
onContextMenuCapture={(e) => e.preventDefault()}
|
2023-08-21 10:52:41 -04:00
|
|
|
onWheel={handleScroll}
|
2023-08-10 16:22:45 -04:00
|
|
|
onPlay={() => setIsLoading(false)}
|
2023-09-06 01:32:53 -04:00
|
|
|
onMouseMoveCapture={handleMouseMove}
|
2023-09-08 17:50:37 +10:00
|
|
|
className={`w-full h-full ${isExecuting && 'blur-md'}`}
|
|
|
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
2023-06-22 16:43:33 +10:00
|
|
|
/>
|
2023-08-10 16:22:45 -04:00
|
|
|
{isLoading && (
|
|
|
|
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
|
|
<Loading>Loading stream...</Loading>
|
|
|
|
</div>
|
|
|
|
)}
|
2023-03-06 20:13:34 +11:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|