Add debounce to image fetch, add loading spinner other polish
related #235
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { IdeContext } from 'src/components/IdeToolbarNew'
|
||||
import { useRef, useState, useEffect, useContext, useMemo } from 'react'
|
||||
import { useRef, useState, useEffect, useContext } from 'react'
|
||||
import { Canvas, extend, useFrame, useThree } from 'react-three-fiber'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
|
||||
extend({ OrbitControls })
|
||||
|
||||
let debounceTimeoutId
|
||||
function Controls({ onCameraChange }) {
|
||||
const controls = useRef()
|
||||
const { scene, camera, gl } = useThree()
|
||||
@@ -12,41 +13,48 @@ function Controls({ onCameraChange }) {
|
||||
// init camera position
|
||||
camera.position.x = 16
|
||||
camera.position.y = 12
|
||||
console.log(camera)
|
||||
camera.fov = 22.5 // matches default openscad fov
|
||||
|
||||
// Order matters with Euler rotations
|
||||
// We want it to rotate around the z or vertical axis first then the x axis to match openscad
|
||||
// in Three.js Y is the vertical axis (Z for openscad)
|
||||
camera.rotation._order = 'YXZ'
|
||||
const getRotations = () => {
|
||||
const { x, y, z } = camera.rotation
|
||||
const rad2Deg = 180 / Math.PI
|
||||
const scadX = (x + Math.PI / 2) * rad2Deg
|
||||
const scadZ = y * rad2Deg
|
||||
const scadY = z * rad2Deg
|
||||
return [scadX, scadY, scadZ]
|
||||
}
|
||||
const getPositions = () => {
|
||||
const { x: scadX, y: scadZ, z: negScadY } = camera.position
|
||||
const scale = 10
|
||||
const scadY = -negScadY
|
||||
return [scadX * scale, scadY * scale, scadZ * scale]
|
||||
}
|
||||
|
||||
if (controls.current) {
|
||||
const callback = ({ target }) => {
|
||||
const getRotations = () => {
|
||||
const { x, y, z } = target.object.rotation
|
||||
const rad2Deg = 180 / Math.PI
|
||||
const scadX = (x + Math.PI / 2) * rad2Deg
|
||||
const scadZ = y * rad2Deg
|
||||
const scadY = z * rad2Deg
|
||||
return [scadX, scadY, scadZ]
|
||||
}
|
||||
const getPositions = () => {
|
||||
const { x: scadX, y: scadZ, z: negScadY } = target.object.position
|
||||
const scale = 10
|
||||
const scadY = -negScadY
|
||||
return [scadX * scale, scadY * scale, scadZ * scale]
|
||||
}
|
||||
const [rx, ry, rz] = getRotations()
|
||||
const [x, y, z] = getPositions()
|
||||
const dragCallback = () => {
|
||||
clearTimeout(debounceTimeoutId)
|
||||
debounceTimeoutId = setTimeout(() => {
|
||||
const [rx, ry, rz] = getRotations()
|
||||
const [x, y, z] = getPositions()
|
||||
|
||||
onCameraChange({
|
||||
position: { x, y, z },
|
||||
rotation: { x: rx, y: ry, z: rz },
|
||||
})
|
||||
onCameraChange({
|
||||
position: { x, y, z },
|
||||
rotation: { x: rx, y: ry, z: rz },
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
controls.current.addEventListener('end', callback)
|
||||
const dragStart = () => clearTimeout(debounceTimeoutId)
|
||||
controls.current.addEventListener('end', dragCallback)
|
||||
controls.current.addEventListener('start', dragStart)
|
||||
const oldCurrent = controls.current
|
||||
return () => oldCurrent.removeEventListener('end', callback)
|
||||
return () => {
|
||||
oldCurrent.removeEventListener('end', dragCallback)
|
||||
oldCurrent.removeEventListener('start', dragStart)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -55,8 +63,6 @@ function Controls({ onCameraChange }) {
|
||||
<orbitControls
|
||||
ref={controls}
|
||||
args={[camera, gl.domElement]}
|
||||
enableDamping
|
||||
dampingFactor={0.1}
|
||||
rotateSpeed={0.5}
|
||||
/>
|
||||
)
|
||||
@@ -77,32 +83,39 @@ let currentCode // I have no idea why this works and using state.code is the dis
|
||||
const IdeViewer = () => {
|
||||
const { state, dispatch } = useContext(IdeContext)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [image, setImage] = useState()
|
||||
|
||||
const image = useMemo(
|
||||
() =>
|
||||
useEffect(() => {
|
||||
setImage(
|
||||
state.objectData?.type === 'png' &&
|
||||
state.objectData?.data &&
|
||||
window.URL.createObjectURL(state.objectData?.data),
|
||||
[state.objectData]
|
||||
)
|
||||
state.objectData?.data &&
|
||||
window.URL.createObjectURL(state.objectData?.data)
|
||||
)
|
||||
setIsDragging(false)
|
||||
}, [state.objectData])
|
||||
currentCode = state.code
|
||||
return (
|
||||
<div className="p-8 border-2 m-2">
|
||||
<div className="relative" style={{ height: '500px', width: '500px' }}>
|
||||
{state.isLoading && (
|
||||
<div className="inset-0 absolute flex items-center justify-center">
|
||||
<div className="h-16 w-16 bg-pink-600 rounded-full animate-ping"></div>
|
||||
</div>
|
||||
)}
|
||||
{image && (
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ opacity: isDragging ? '0%' : '100%' }}
|
||||
className={`absolute inset-0 transition-opacity duration-500 ${
|
||||
isDragging ? 'opacity-25' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
<img src={image} className="" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`opacity-0 absolute inset-0 ${
|
||||
className={`opacity-0 opacity- absolute inset-0 transition-opacity duration-500 ${
|
||||
isDragging ? 'opacity-100' : 'hover:opacity-50'
|
||||
}`}
|
||||
onMouseDown={() => setIsDragging(true)}
|
||||
onMouseUp={() => setIsDragging(false)}
|
||||
>
|
||||
<Canvas>
|
||||
<Controls
|
||||
@@ -121,9 +134,17 @@ const IdeViewer = () => {
|
||||
/>
|
||||
<ambientLight />
|
||||
<pointLight position={[15, 5, 10]} />
|
||||
<Box position={[0, 5, 0]} size={[0.1, 10, 0.1]} color="cyan" />
|
||||
<Box position={[0, 0, -5]} size={[0.1, 0.1, 10]} color="orange" />
|
||||
<Box position={[5, 0, 0]} size={[10, 0.1, 0.1]} color="hotpink" />
|
||||
<Box position={[0, 4.95, 0]} size={[0.1, 10, 0.1]} color="cyan" />
|
||||
<Box
|
||||
position={[0, 0, -5.05]}
|
||||
size={[0.1, 0.1, 10]}
|
||||
color="orange"
|
||||
/>
|
||||
<Box
|
||||
position={[5.05, 0, 0]}
|
||||
size={[10, 0.1, 0.1]}
|
||||
color="hotpink"
|
||||
/>
|
||||
</Canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ export const useIdeState = () => {
|
||||
splitPercentage: 70,
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
}
|
||||
const reducer = (state, { type, payload }) => {
|
||||
switch (type) {
|
||||
@@ -38,6 +39,7 @@ export const useIdeState = () => {
|
||||
consoleMessages: payload.message
|
||||
? [...state.consoleMessages, payload.message]
|
||||
: payload.message,
|
||||
isLoading: false,
|
||||
}
|
||||
case 'errorRender':
|
||||
return {
|
||||
@@ -45,6 +47,7 @@ export const useIdeState = () => {
|
||||
consoleMessages: payload.message
|
||||
? [...state.consoleMessages, payload.message]
|
||||
: payload.message,
|
||||
isLoading: false,
|
||||
}
|
||||
case 'setIdeType':
|
||||
return {
|
||||
@@ -56,6 +59,11 @@ export const useIdeState = () => {
|
||||
...state,
|
||||
layout: payload.message,
|
||||
}
|
||||
case 'setLoading':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
@@ -83,6 +91,7 @@ export const useIdeState = () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
dispatch({ type: 'setLoading' })
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user