Add debounce to image fetch, add loading spinner other polish

related #235
This commit is contained in:
Kurt Hutten
2021-03-12 18:54:17 +11:00
parent 5094996a02
commit d3e7012669
2 changed files with 70 additions and 40 deletions

View File

@@ -1,10 +1,11 @@
import { IdeContext } from 'src/components/IdeToolbarNew' 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 { Canvas, extend, useFrame, useThree } from 'react-three-fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
extend({ OrbitControls }) extend({ OrbitControls })
let debounceTimeoutId
function Controls({ onCameraChange }) { function Controls({ onCameraChange }) {
const controls = useRef() const controls = useRef()
const { scene, camera, gl } = useThree() const { scene, camera, gl } = useThree()
@@ -12,41 +13,48 @@ function Controls({ onCameraChange }) {
// init camera position // init camera position
camera.position.x = 16 camera.position.x = 16
camera.position.y = 12 camera.position.y = 12
console.log(camera)
camera.fov = 22.5 // matches default openscad fov camera.fov = 22.5 // matches default openscad fov
// Order matters with Euler rotations // Order matters with Euler rotations
// We want it to rotate around the z or vertical axis first then the x axis to match openscad // 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) // in Three.js Y is the vertical axis (Z for openscad)
camera.rotation._order = 'YXZ' 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) { if (controls.current) {
const callback = ({ target }) => { const dragCallback = () => {
const getRotations = () => { clearTimeout(debounceTimeoutId)
const { x, y, z } = target.object.rotation debounceTimeoutId = setTimeout(() => {
const rad2Deg = 180 / Math.PI const [rx, ry, rz] = getRotations()
const scadX = (x + Math.PI / 2) * rad2Deg const [x, y, z] = getPositions()
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()
onCameraChange({ onCameraChange({
position: { x, y, z }, position: { x, y, z },
rotation: { x: rx, y: ry, z: rz }, 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 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 <orbitControls
ref={controls} ref={controls}
args={[camera, gl.domElement]} args={[camera, gl.domElement]}
enableDamping
dampingFactor={0.1}
rotateSpeed={0.5} 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 IdeViewer = () => {
const { state, dispatch } = useContext(IdeContext) const { state, dispatch } = useContext(IdeContext)
const [isDragging, setIsDragging] = useState(false) const [isDragging, setIsDragging] = useState(false)
const [image, setImage] = useState()
const image = useMemo( useEffect(() => {
() => setImage(
state.objectData?.type === 'png' && state.objectData?.type === 'png' &&
state.objectData?.data && state.objectData?.data &&
window.URL.createObjectURL(state.objectData?.data), window.URL.createObjectURL(state.objectData?.data)
[state.objectData] )
) setIsDragging(false)
}, [state.objectData])
currentCode = state.code currentCode = state.code
return ( return (
<div className="p-8 border-2 m-2"> <div className="p-8 border-2 m-2">
<div className="relative" style={{ height: '500px', width: '500px' }}> <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 && ( {image && (
<div <div
className="absolute inset-0" className={`absolute inset-0 transition-opacity duration-500 ${
style={{ opacity: isDragging ? '0%' : '100%' }} isDragging ? 'opacity-25' : 'opacity-100'
}`}
> >
<img src={image} className="" /> <img src={image} className="" />
</div> </div>
)} )}
<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' isDragging ? 'opacity-100' : 'hover:opacity-50'
}`} }`}
onMouseDown={() => setIsDragging(true)} onMouseDown={() => setIsDragging(true)}
onMouseUp={() => setIsDragging(false)}
> >
<Canvas> <Canvas>
<Controls <Controls
@@ -121,9 +134,17 @@ const IdeViewer = () => {
/> />
<ambientLight /> <ambientLight />
<pointLight position={[15, 5, 10]} /> <pointLight position={[15, 5, 10]} />
<Box position={[0, 5, 0]} size={[0.1, 10, 0.1]} color="cyan" /> <Box position={[0, 4.95, 0]} size={[0.1, 10, 0.1]} color="cyan" />
<Box position={[0, 0, -5]} size={[0.1, 0.1, 10]} color="orange" /> <Box
<Box position={[5, 0, 0]} size={[10, 0.1, 0.1]} color="hotpink" /> 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> </Canvas>
</div> </div>
</div> </div>

View File

@@ -23,6 +23,7 @@ export const useIdeState = () => {
splitPercentage: 70, splitPercentage: 70,
}, },
}, },
isLoading: false,
} }
const reducer = (state, { type, payload }) => { const reducer = (state, { type, payload }) => {
switch (type) { switch (type) {
@@ -38,6 +39,7 @@ export const useIdeState = () => {
consoleMessages: payload.message consoleMessages: payload.message
? [...state.consoleMessages, payload.message] ? [...state.consoleMessages, payload.message]
: payload.message, : payload.message,
isLoading: false,
} }
case 'errorRender': case 'errorRender':
return { return {
@@ -45,6 +47,7 @@ export const useIdeState = () => {
consoleMessages: payload.message consoleMessages: payload.message
? [...state.consoleMessages, payload.message] ? [...state.consoleMessages, payload.message]
: payload.message, : payload.message,
isLoading: false,
} }
case 'setIdeType': case 'setIdeType':
return { return {
@@ -56,6 +59,11 @@ export const useIdeState = () => {
...state, ...state,
layout: payload.message, layout: payload.message,
} }
case 'setLoading':
return {
...state,
isLoading: true,
}
default: default:
return state return state
} }
@@ -83,6 +91,7 @@ export const useIdeState = () => {
}) })
} }
}) })
dispatch({ type: 'setLoading' })
break break
default: default: