Overhaul social card (again) #541

Merged
Irev-Dev merged 1 commits from kurt/overhaul-social-card into main 2021-10-11 21:09:56 +02:00
13 changed files with 454 additions and 324 deletions

View File

@@ -1,14 +1,15 @@
import { useState } from 'react'
import { toast } from '@redwoodjs/web/toast'
import Popover from '@material-ui/core/Popover'
import Svg from 'src/components/Svg/Svg'
import Button from 'src/components/Button/Button'
import { toJpeg } from 'html-to-image'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import { canvasToBlob, blobTo64 } from 'src/helpers/canvasToBlob'
import { useUpdateProjectImages } from 'src/helpers/hooks/useUpdateProjectImages'
import { requestRenderStateless } from 'src/helpers/hooks/useIdeState'
import { PureIdeViewer } from 'src/components/IdeViewer/IdeViewer'
import SocialCardCell from 'src/components/SocialCardCell/SocialCardCell'
import { toJpeg } from 'html-to-image'
export const captureSize = { width: 500, height: 522 }
const anchorOrigin = {
vertical: 'bottom',
@@ -19,175 +20,247 @@ const transformOrigin = {
horizontal: 'center',
}
const CaptureButton = ({
canEdit,
TheButton,
shouldUpdateImage,
projectTitle,
userName,
export const CaptureButtonViewer = ({
onInit,
onScadImage,
canvasRatio = 1,
}: {
onInit: (a: any) => void
onScadImage: (a: any) => void
canvasRatio: number
}) => {
const [captureState, setCaptureState] = useState<any>({})
const [anchorEl, setAnchorEl] = useState(null)
const [whichPopup, setWhichPopup] = useState(null)
const { state, project } = useIdeContext()
const ref = React.useRef<HTMLDivElement>(null)
const { updateProjectImages } = useUpdateProjectImages({})
const onCapture = async () => {
const threeInstance = state.threeInstance
const isOpenScadImage = state?.objectData?.type === 'png'
let imgBlob
let image64
if (!isOpenScadImage) {
imgBlob = canvasToBlob(threeInstance, { width: 500, height: 375 })
image64 = blobTo64(
await canvasToBlob(threeInstance, { width: 500, height: 522 })
)
} else {
imgBlob = state.objectData.data
image64 = blobTo64(state.objectData.data)
}
const config = {
image: await imgBlob,
currImage: project?.mainImage,
imageObjectURL: window.URL.createObjectURL(await imgBlob),
callback: uploadAndUpdateImage,
cloudinaryImgURL: '',
updated: false,
image64: await image64,
}
setCaptureState(config)
async function uploadAndUpdateImage() {
const upload = async () => {
const socialCard64 = toJpeg(ref.current, {
cacheBust: true,
quality: 0.7,
})
// uploading in two separate mutations because of the 100kb limit of the lambda functions
const imageUploadPromise1 = updateProjectImages({
variables: {
id: project?.id,
mainImage64: await config.image64,
},
})
const imageUploadPromise2 = updateProjectImages({
variables: {
id: project?.id,
socialCard64: await socialCard64,
},
})
return Promise.all([imageUploadPromise2, imageUploadPromise1])
}
const promise = upload()
toast.promise(promise, {
loading: 'Saving Image/s',
success: <b>Image/s saved!</b>,
error: <b>Problem saving.</b>,
const { state } = useIdeContext()
const threeInstance = React.useRef(null)
const [dataType, dataTypeSetter] = useState(state?.objectData?.type)
const [artifact, artifactSetter] = useState(state?.objectData?.data)
const [isLoading, isLoadingSetter] = useState(false)
const getThreeInstance = (_threeInstance) => {
threeInstance.current = _threeInstance
onInit(_threeInstance)
}
const onCameraChange = (camera) => {
const renderPromise =
state.ideType === 'openscad' &&
requestRenderStateless({
state,
camera,
viewerSize: {
width: threeInstance.current.size.width * canvasRatio,
height: threeInstance.current.size.height * canvasRatio,
},
})
const [{ data }] = await promise
return data?.updateProjectImages?.mainImage
}
// if there isn't a screenshot saved yet, just go ahead and save right away
if (shouldUpdateImage) {
config.cloudinaryImgURL = await uploadAndUpdateImage()
config.updated = true
setCaptureState(config)
if (!renderPromise) {
return
}
isLoadingSetter(true)
renderPromise.then(async ({ objectData }) => {
isLoadingSetter(false)
dataTypeSetter(objectData?.type)
artifactSetter(objectData?.data)
if (objectData?.type === 'png') {
onScadImage(await blobTo64(objectData?.data))
}
})
}
const handleClick = ({ event, whichPopup }) => {
setAnchorEl(event.currentTarget)
setWhichPopup(whichPopup)
}
const handleClose = () => {
setAnchorEl(null)
setWhichPopup(null)
}
return (
<div>
{canEdit && (
<div>
<TheButton
onClick={async (event) => {
handleClick({ event, whichPopup: 'capture' })
onCapture()
}}
/>
<Popover
id={'capture-popover'}
open={whichPopup === 'capture'}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={anchorOrigin}
transformOrigin={transformOrigin}
className="material-ui-overrides transform translate-y-4"
>
<div className="text-sm p-4 text-gray-500">
{!captureState ? (
'Loading...'
) : (
<div className="">
<div className="text-lg">Thumbnail</div>
<div
className="rounded"
style={{ width: 'fit-content', overflow: 'hidden' }}
>
<img src={captureState.imageObjectURL} className="w-32" />
</div>
</div>
)}
<div className="text-lg mt-4">Social Media Card</div>
<div className="rounded-lg shadow-md overflow-hidden">
<div
className="transform scale-50 origin-top-left"
style={{ width: '600px', height: '315px' }}
>
<div style={{ width: '1200px', height: '630px' }} ref={ref}>
<SocialCardCell
userName={userName}
projectTitle={projectTitle}
image64={captureState.image64}
/>
</div>
</div>
</div>
<div className="mt-4 text-indigo-800">
{captureState.currImage && !captureState.updated ? (
<Button
iconName="refresh"
className="shadow-md hover:shadow-lg border-indigo-600 border-2 border-opacity-0 hover:border-opacity-100 bg-indigo-200 text-indigo-100 text-opacity-100 bg-opacity-80"
shouldAnimateHover
onClick={async () => {
const cloudinaryImg = await captureState.callback()
setCaptureState({
...captureState,
currImage: cloudinaryImg,
updated: true,
})
}}
>
Update Project Images
</Button>
) : (
<div className="flex justify-center mb-4">
<Svg
name="checkmark"
className="mr-2 w-6 text-indigo-600"
/>{' '}
Project Images Updated
</div>
)}
</div>
</div>
</Popover>
</div>
)}
<PureIdeViewer
scadRatio={canvasRatio}
dataType={dataType}
artifact={artifact}
onInit={getThreeInstance}
onCameraChange={onCameraChange}
isLoading={isLoading}
isMinimal
/>
)
}
function TabContent() {
return (
<div className="bg-ch-gray-800 h-full overflow-y-auto px-8 pb-16">
<IsolatedCanvas
size={{ width: 500, height: 375 }}
uploadKey="mainImage64"
RenderComponent={ThumbnailViewer}
/>
<IsolatedCanvas
canvasRatio={2}
size={captureSize}
uploadKey="socialCard64"
RenderComponent={SocialCardLiveViewer}
/>
</div>
)
}
export default CaptureButton
function SocialCardLiveViewer({
forwardRef,
onUpload,
children,
partSnapShot64,
}) {
const { project } = useIdeContext()
return (
<>
<h3 className="text-2xl text-ch-gray-300 pt-4">Set social Image</h3>
<div className="flex py-4">
<div className="rounded-md shadow-ch border border-gray-400 overflow-hidden">
<div
className="transform scale-50 origin-top-left"
style={{ width: '600px', height: '315px' }}
>
<div style={{ width: '1200px', height: '630px' }} ref={forwardRef}>
<SocialCardCell
userName={project.user.userName}
projectTitle={project.title}
image64={partSnapShot64}
LiveProjectViewer={() => children}
/>
</div>
</div>
</div>
</div>
<button className="bg-gray-200 p-2 rounded-sm" onClick={onUpload}>
save image
</button>
</>
)
}
function ThumbnailViewer({ forwardRef, onUpload, children, partSnapShot64 }) {
return (
<>
<h3 className="text-2xl text-ch-gray-300 pt-4">Set thumbnail</h3>
<div
style={{ width: '500px', height: '375px' }}
className="rounded-md shadow-ch border border-gray-400 overflow-hidden my-4"
>
franknoirot commented 2021-10-10 23:04:25 +02:00 (Migrated from github.com)
Review

@Irev-Dev could this component be used for a /view endpoint?

@Irev-Dev could this component be used for a /view endpoint?
Irev-Dev commented 2021-10-11 21:07:18 +02:00 (Migrated from github.com)
Review

Yeah I suppose so 👍

Yeah I suppose so 👍
<div className="h-full w-full relative" ref={forwardRef}>
{children}
{partSnapShot64 && (
<img src={partSnapShot64} className="absolute inset-0" />
)}
</div>
</div>
<button className="bg-gray-200 p-2 rounded-sm" onClick={onUpload}>
save thumbnail
</button>
</>
)
}
function IsolatedCanvas({
RenderComponent,
canvasRatio = 1,
size,
uploadKey,
}: {
canvasRatio?: number
uploadKey: 'socialCard64' | 'mainImage64'
size: {
width: number
height: number
}
RenderComponent: React.FC<{
forwardRef: React.Ref<any>
children: React.ReactNode
partSnapShot64: string
onUpload: (a: any) => void
}>
}) {
const { project } = useIdeContext()
const { updateProjectImages } = useUpdateProjectImages({})
const [partSnapShot64, partSnapShot64Setter] = React.useState('')
const [scadSnapShot64, scadSnapShot64Setter] = React.useState('')
const captureRef = React.useRef<HTMLDivElement>(null)
const threeInstance = React.useRef(null)
const onInit = (_threeInstance) => (threeInstance.current = _threeInstance)
const upload = async () => {
const uploadPromise = new Promise((resolve, reject) => {
const asyncHelper = async () => {
if (!scadSnapShot64) {
partSnapShot64Setter(
await blobTo64(await canvasToBlob(threeInstance.current, size))
)
} else {
partSnapShot64Setter(scadSnapShot64)
}
setTimeout(async () => {
const capturedImage = await toJpeg(captureRef.current, {
cacheBust: true,
quality: 0.7,
})
await updateProjectImages({
variables: {
id: project?.id,
[uploadKey]: capturedImage,
},
})
partSnapShot64Setter('')
resolve(capturedImage)
})
}
asyncHelper()
Irev-Dev commented 2021-10-09 20:37:42 +02:00 (Migrated from github.com)
Review

This might have been more effort than what it was worth. The social image need to be captured at a rather large size 1200x630, so the aim here is to scale ti down by half so that for the viewer so that it's not dominating the UI.
However the three canvas can gets confused and ends up halving again, which is why you'll see the canvasRatio sprinkled around to try and fix things.

This might have been more effort than what it was worth. The social image need to be captured at a rather large size 1200x630, so the aim here is to scale ti down by half so that for the viewer so that it's not dominating the UI. However the three canvas can gets confused and ends up halving again, which is why you'll see the `canvasRatio` sprinkled around to try and fix things.
})
toast.promise(uploadPromise, {
loading: 'Saving Image',
success: (finalImg: string) => (
<div className="flex flex-col items-center">
<b className="py-2">Image saved!</b>
Irev-Dev commented 2021-10-09 20:41:29 +02:00 (Migrated from github.com)
Review

even though we're using the canvas directly (passed in through LiveProjectViewer), this doesn't work when capturing with import { toJpeg } from 'html-to-image' and so the canvas need to be captured, than the image inserted over the canvas via partSnapShot64 so that we can than use toJpeg

even though we're using the canvas directly (passed in through `LiveProjectViewer`), this doesn't work when capturing with `import { toJpeg } from 'html-to-image'` and so the canvas need to be captured, than the image inserted over the canvas via `partSnapShot64` so that we can than use `toJpeg`
<img src={finalImg} />
</div>
),
error: <b>Problem saving.</b>,
})
}
return (
<div>
<RenderComponent
forwardRef={captureRef}
onUpload={upload}
partSnapShot64={partSnapShot64}
>
<div
style={{
width: `${size.width * canvasRatio}px`,
height: `${size.height * canvasRatio}px`,
}}
>
<CaptureButtonViewer
onInit={onInit}
onScadImage={scadSnapShot64Setter}
canvasRatio={canvasRatio}
/>
</div>
</RenderComponent>
</div>
)
}
export default function CaptureButton({ TheButton }) {
const { state, thunkDispatch } = useIdeContext()
return (
<TheButton
onClick={() => {
thunkDispatch({
type: 'addEditorModel',
payload: {
type: 'component',
label: 'Social Media Card',
Component: TabContent,
},
})
thunkDispatch({
type: 'switchEditorModel',
payload: state.editorTabs.length,
})
}}
/>
)
}

View File

@@ -62,7 +62,7 @@ const EditorMenu = () => {
})
thunkDispatch({
type: 'switchEditorModel',
payload: state.models.length,
payload: state.editorTabs.length,
})
}}
/>

View File

@@ -74,12 +74,8 @@ export const makeStlDownloadHandler =
requestRender({
state,
dispatch,
code: state.code,
viewerSize: state.viewerSize,
camera: state.camera,
quality: 'high',
specialCadProcess,
parameters: state.currentParameters,
}).then(
(result) => result && saveFile(makeStlBlobFromGeo(result.data))
)

View File

@@ -8,7 +8,7 @@ import PanelToolbar from 'src/components/PanelToolbar/PanelToolbar'
import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize'
const IdeEditor = lazy(() => import('src/components/IdeEditor/IdeEditor'))
const IdeViewer = lazy(() => import('src/components/IdeViewer/IdeViewer'))
import IdeViewer from 'src/components/IdeViewer/IdeViewer'
const SmallLoadingPing = (
<div className="bg-ch-gray-800 text-gray-200 font-ropa-sans relative w-full h-full flex justify-center items-center">
@@ -33,11 +33,7 @@ const ELEMENT_MAP = {
<IdeEditor Loading={SmallLoadingPing} />
</Suspense>
),
Viewer: (
<Suspense fallback={BigLoadingPing}>
<IdeViewer Loading={BigLoadingPing} />
</Suspense>
),
Viewer: <IdeViewer />,
Console: <IdeConsole />,
}

View File

@@ -60,24 +60,21 @@ const IdeEditor = ({ Loading }) => {
requestRender({
state,
dispatch,
code: state.code,
viewerSize: state.viewerSize,
camera: state.camera,
parameters: state.currentParameters,
})
})
localStorage.setItem(makeCodeStoreKey(state.ideType), state.code)
}
}
const currentTab = state.editorTabs[state.currentModel]
return (
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
className="h-full"
onKeyDown={handleSaveHotkey}
>
{state.models.length > 1 && (
{state.editorTabs.length > 1 && (
<fieldset className="bg-ch-gray-700 text-ch-gray-300 flex m-0 p-0">
{state.models.map((model, i) => (
{state.editorTabs.map((model, i) => (
<label
key={model.type + '-' + i}
className={
@@ -117,7 +114,7 @@ const IdeEditor = ({ Loading }) => {
))}
</fieldset>
)}
{state.models[state.currentModel].type === 'code' ? (
{currentTab.type === 'code' && (
<Editor
defaultValue={state.code}
value={state.code}
@@ -128,11 +125,13 @@ const IdeEditor = ({ Loading }) => {
language={ideTypeToLanguageMap[state.ideType] || 'cpp'}
onChange={handleCodeChange}
/>
) : (
)}
{currentTab.type === 'guide' && (
<div className="bg-ch-gray-800 h-full">
<EditorGuide content={state.models[state.currentModel].content} />
<EditorGuide content={currentTab.content} />
</div>
)}
{currentTab.type === 'component' && <currentTab.Component />}
</div>
)
}

View File

@@ -12,6 +12,7 @@ import CaptureButton from 'src/components/CaptureButton/CaptureButton'
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import Gravatar from 'src/components/Gravatar/Gravatar'
import EditableProjectTitle from 'src/components/EditableProjecTitle/EditableProjecTitle'
import SocialCardModal from 'src/components/SocialCardModal/SocialCardModal'
const FORK_PROJECT_MUTATION = gql`
mutation ForkProjectMutation($input: ForkProjectInput!) {
@@ -121,10 +122,6 @@ export default function IdeHeader({
<div className="grid grid-flow-col-dense gap-4 items-center mr-4">
{canEdit && !isProfile && (
<CaptureButton
canEdit={canEdit}
projectTitle={project?.title}
userName={project?.user?.userName}
shouldUpdateImage={!project?.mainImage}
TheButton={({ onClick }) => (
<TopButton
onClick={onClick}

View File

@@ -1,13 +1,12 @@
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
import * as THREE from 'three'
import { useRef, useState, useEffect } from 'react'
import { useRef, useState, useEffect, Suspense } from 'react'
import { Canvas, useThree } from '@react-three/fiber'
import {
PerspectiveCamera,
GizmoHelper,
GizmoViewport,
OrbitControls,
Environment,
useTexture,
} from '@react-three/drei'
import { useEdgeSplit } from 'src/helpers/hooks/useEdgeSplit'
@@ -16,6 +15,7 @@ import { requestRender } from 'src/helpers/hooks/useIdeState'
import texture from './dullFrontLitMetal.png'
import Customizer from 'src/components/Customizer/Customizer'
import DelayedPingAnimation from 'src/components/DelayedPingAnimation/DelayedPingAnimation'
import type { ArtifactTypes } from 'src/helpers/cadPackages/common'
const thresholdAngle = 12
@@ -35,13 +35,13 @@ function Asset({ geometry: incomingGeo }) {
<group dispose={null}>
<mesh ref={mesh} scale={[1, 1, 1]} geometry={incomingGeo}>
<meshPhysicalMaterial
envMapIntensity={2}
envMapIntensity={0.1}
color="#F472B6"
map={colorMap}
clearcoat={0.1}
clearcoatRoughness={0.2}
roughness={10}
metalness={0.9}
metalness={0.7}
smoothShading
/>
</mesh>
@@ -148,42 +148,53 @@ function Sphere(props) {
)
}
const IdeViewer = ({ Loading }) => {
const { state, thunkDispatch } = useIdeContext()
export function PureIdeViewer({
dataType,
artifact,
onInit,
onCameraChange,
isLoading,
isMinimal = false,
scadRatio = 1,
}: {
dataType: 'INIT' | ArtifactTypes
artifact: any
isLoading: boolean
onInit: Function
onCameraChange: Function
isMinimal?: boolean
scadRatio?: number
}) {
const [isDragging, setIsDragging] = useState(false)
const [image, setImage] = useState()
const onInit = (threeInstance) => {
thunkDispatch({ type: 'setThreeInstance', payload: threeInstance })
}
useEffect(() => {
setImage(state.objectData?.type === 'png' && state.objectData?.data)
setImage(dataType === 'png' && artifact)
setIsDragging(false)
}, [state.objectData?.type, state.objectData?.data])
}, [dataType, artifact])
const PrimitiveArray = React.useMemo(
() =>
state.objectData?.type === 'primitive-array' && state.objectData?.data,
[state.objectData?.type, state.objectData?.data]
dataType === 'primitive-array' && artifact?.map((mesh) => mesh.clone()),
[dataType, artifact]
)
// the following are tailwind colors in hex, can't use these classes to color three.js meshes.
const pink400 = '#F472B6'
const indigo300 = '#A5B4FC'
const indigo900 = '#312E81'
const jscadLightIntensity =
state.objectData?.type === 'geometry' &&
state.objectData?.data &&
state.objectData?.data.length
? 0.5
: 1.2
const jscadLightIntensity = PrimitiveArray ? 0.5 : 1.1
return (
<div className="relative h-full bg-ch-gray-800">
<div className="relative h-full bg-ch-gray-800 cursor-grab">
{image && (
<div
className={`absolute inset-0 transition-opacity duration-500 ${
isDragging ? 'opacity-25' : 'opacity-100'
}`}
Irev-Dev commented 2021-10-09 20:25:56 +02:00 (Migrated from github.com)
Review

Changes the IDE view so that it doesn't rely on state/context so that multiple can be used together, it can than be wrapped in a component that provides it with state for the main IDE case.

Changes the IDE view so that it doesn't rely on state/context so that multiple can be used together, it can than be wrapped in a component that provides it with state for the main IDE case.
franknoirot commented 2021-10-10 23:14:15 +02:00 (Migrated from github.com)
Review

This is a great decoupling move!

This is a great decoupling move!
style={{
transform: `translate(${
scadRatio !== 1 ? '-250px, -261px' : '0px, 0px'
})`,
}}
>
<img
alt="code-cad preview"
@@ -195,7 +206,7 @@ const IdeViewer = ({ Loading }) => {
)}
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
!(isDragging || state.objectData?.type !== 'png')
!(isDragging || dataType !== 'png')
? 'hover:opacity-50'
: 'opacity-100'
}`}
@@ -205,26 +216,7 @@ const IdeViewer = ({ Loading }) => {
<Controls
onDragStart={() => setIsDragging(true)}
onInit={onInit}
onCameraChange={(camera) => {
thunkDispatch({
type: 'updateCamera',
payload: { camera },
})
thunkDispatch((dispatch, getState) => {
const state = getState()
if (['png', 'INIT'].includes(state.objectData?.type)) {
dispatch({ type: 'setLoading' })
requestRender({
state,
dispatch,
code: state.code,
viewerSize: state.viewerSize,
camera,
parameters: state.currentParameters,
})
}
})
}}
onCameraChange={onCameraChange}
/>
<PerspectiveCamera makeDefault up={[0, 0, 1]}>
<pointLight
@@ -232,17 +224,16 @@ const IdeViewer = ({ Loading }) => {
intensity={jscadLightIntensity}
/>
</PerspectiveCamera>
<ambientLight intensity={0.3} />
<Environment preset="warehouse" />
<ambientLight intensity={2 * jscadLightIntensity} />
<pointLight
position={[-1000, -1000, -1000]}
color="#5555FF"
intensity={0.5}
intensity={1 * jscadLightIntensity}
/>
<pointLight
position={[-1000, 0, 1000]}
color="#5555FF"
intensity={0.5}
intensity={1 * jscadLightIntensity}
/>
<gridHelper
args={[200, 20, 0xff5555, 0x555555]}
@@ -250,13 +241,15 @@ const IdeViewer = ({ Loading }) => {
material-transparent
rotation-x={Math.PI / 2}
/>
<GizmoHelper alignment={'top-left'} margin={[80, 80]}>
<GizmoViewport
axisColors={['red', 'green', 'blue']}
labelColor="black"
/>
</GizmoHelper>
{state.objectData?.type === 'png' && (
{!isMinimal && (
<GizmoHelper alignment={'top-left'} margin={[80, 80]}>
<GizmoViewport
axisColors={['red', 'green', 'blue']}
labelColor="black"
/>
</GizmoHelper>
)}
{dataType === 'png' && (
<>
<Sphere position={[0, 0, 0]} color={pink400} />
<Box position={[0, 50, 0]} size={[1, 100, 1]} color={indigo900} />
@@ -264,8 +257,10 @@ const IdeViewer = ({ Loading }) => {
<Box position={[50, 0, 0]} size={[100, 1, 1]} color={pink400} />
</>
)}
{state.objectData?.type === 'geometry' && state.objectData?.data && (
<Asset geometry={state.objectData?.data} />
{dataType === 'geometry' && artifact && (
<Suspense fallback={null}>
<Asset geometry={artifact} />
</Suspense>
)}
{PrimitiveArray &&
PrimitiveArray.map((mesh, index) => (
@@ -273,10 +268,55 @@ const IdeViewer = ({ Loading }) => {
))}
</Canvas>
</div>
<DelayedPingAnimation isLoading={state.isLoading} />
<Customizer />
<DelayedPingAnimation isLoading={isLoading} />
{!isMinimal && <Customizer />}
</div>
)
}
const IdeViewer = ({
handleOwnCamera = false,
}: {
handleOwnCamera?: boolean
}) => {
const { state, thunkDispatch } = useIdeContext()
const dataType = state.objectData?.type
const artifact = state.objectData?.data
const onInit = (threeInstance) => {
thunkDispatch({ type: 'setThreeInstance', payload: threeInstance })
}
const onCameraChange = (camera) => {
if (handleOwnCamera) {
console.log('yo')
return
}
thunkDispatch({
type: 'updateCamera',
payload: { camera },
})
thunkDispatch((dispatch, getState) => {
const state = getState()
if (['png', 'INIT'].includes(state?.objectData?.type)) {
dispatch({ type: 'setLoading' })
requestRender({
state,
dispatch,
camera,
})
}
})
}
return (
<PureIdeViewer
dataType={dataType}
artifact={artifact}
onInit={onInit}
onCameraChange={onCameraChange}
isLoading={state.isLoading}
/>
)
}
export default IdeViewer

View File

@@ -10,9 +10,6 @@ export const useRender = () => {
requestRender({
state,
dispatch,
code: state.code,
viewerSize: state.viewerSize,
camera: state.camera,
parameters: disableParams ? {} : state.currentParameters,
})
})

View File

@@ -1,15 +1,11 @@
import { lazy, Suspense } from 'react'
const IdeViewer = lazy(() => import('src/components/IdeViewer/IdeViewer'))
import IdeViewer from 'src/components/IdeViewer/IdeViewer'
import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize'
import { BigLoadingPing } from 'src/components/IdeContainer/IdeContainer'
const ProfileViewer = () => {
const { viewerDomRef } = use3dViewerResize()
return (
<div className="h-full" ref={viewerDomRef}>
<Suspense fallback={BigLoadingPing}>
<IdeViewer Loading={BigLoadingPing} />
</Suspense>
<IdeViewer />
</div>
)
}

View File

@@ -37,7 +37,7 @@ export const Failure = ({ error }: CellFailureProps) => (
export const Success = ({
userProject,
variables: { image64 },
variables: { image64, LiveProjectViewer },
}: CellSuccessProps<FindSocialCardQuery>) => {
const image = userProject?.Project?.mainImage
const gravatar = userProject?.image
@@ -47,7 +47,7 @@ export const Success = ({
: userProject?.Project?.description || ''
return (
<div
className="grid h-screen bg-ch-gray-800 text-ch-gray-300"
className="grid h-full bg-ch-gray-800 text-ch-gray-300"
id="social-card-loaded"
style={{ gridTemplateRows: ' 555fr 18fr' }}
>
@@ -96,6 +96,18 @@ export const Success = ({
/>
)}
</div>
<div
className={`absolute inset-0 flex items-center justify-center ${
image64 && 'opacity-0'
}`}
>
{LiveProjectViewer && (
<div className="w-full h-full" id="social-card-canvas">
<LiveProjectViewer />
</div>
)}
</div>
</div>
</div>
<div

View File

@@ -26,7 +26,7 @@ export const canvasToBlob = async (
(blob) => {
resolve(blob)
},
'image/jpeg',
'image/png',
0.75
)
})

View File

@@ -23,10 +23,7 @@ export const use3dViewerResize = () => {
requestRender({
state,
dispatch,
code: state.code,
viewerSize: { width, height },
camera: state.camera,
parameters: state.currentParameters,
})
}
})

View File

@@ -25,19 +25,33 @@ interface XYZ {
z: number
}
interface EditorModel {
type: 'code' | 'guide'
interface CodeTab {
type: 'code'
label: string
content?: string
}
interface GuideTab {
type: 'guide'
label: string
content: string
}
interface ComponentTab {
type: 'component'
label: string
Component: React.FC
Irev-Dev commented 2021-10-09 20:29:42 +02:00 (Migrated from github.com)
Review

Added a tab type that will render a component.

Added a tab type that will render a component.
}
type EditorTab = GuideTab | CodeTab | ComponentTab
export interface State {
ideType: 'INIT' | CadPackageType
viewerContext: 'ide' | 'viewer'
ideGuide?: string
consoleMessages: { type: 'message' | 'error'; message: string; time: Date }[]
code: string
models: EditorModel[]
editorTabs: EditorTab[]
currentModel: number
objectData: {
type: 'INIT' | ArtifactTypes
@@ -78,7 +92,7 @@ export const initialState: State = {
{ type: 'message', message: 'Initialising', time: new Date() },
],
code,
models: [{ type: 'code', label: 'Code' }],
editorTabs: [{ type: 'code', label: 'Code' }],
currentModel: 0,
objectData: {
type: 'INIT',
@@ -240,14 +254,14 @@ const reducer = (state: State, { type, payload }): State => {
case 'addEditorModel':
return {
...state,
models: [...state.models, payload],
editorTabs: [...state.editorTabs, payload],
}
case 'removeEditorModel':
return {
...state,
models: [
...state.models.slice(0, payload),
...state.models.slice(payload + 1),
editorTabs: [
...state.editorTabs.slice(0, payload),
...state.editorTabs.slice(payload + 1),
],
currentModel: payload === 0 ? 0 : payload - 1,
}
@@ -284,71 +298,84 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
return [state, thunkDispatch]
}
interface RequestRenderArgs {
interface RequestRenderArgsStateless {
state: State
dispatch: any
parameters: any
code: State['code']
camera: State['camera']
viewerSize: State['viewerSize']
camera?: State['camera']
viewerSize?: State['viewerSize']
quality?: State['objectData']['quality']
specialCadProcess?: string
parameters?: { [key: string]: any }
}
export const requestRender = ({
export const requestRenderStateless = ({
state,
dispatch,
code,
camera,
viewerSize,
quality = 'low',
specialCadProcess = null,
parameters,
}: RequestRenderArgs) => {
}: RequestRenderArgsStateless): null | Promise<any> => {
if (
state.ideType !== 'INIT' &&
(!state.isLoading || state.objectData?.type === 'INIT')
!(
state.ideType !== 'INIT' &&
(!state.isLoading || state.objectData?.type === 'INIT')
)
) {
const renderFn = specialCadProcess
? cadPackages[state.ideType][specialCadProcess]
: cadPackages[state.ideType].render
return renderFn({
code,
settings: {
parameters: state.isCustomizerOpen ? parameters : {},
camera,
viewerSize,
quality,
},
})
.then(
({
objectData,
message,
status,
customizerParams,
currentParameters,
}) => {
if (status === 'error') {
dispatch({
type: 'errorRender',
payload: { message },
})
} else {
dispatch({
type: 'healthyRender',
payload: {
objectData,
message,
lastRunCode: code,
customizerParams,
currentParameters,
},
})
return objectData
}
}
)
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
return null
}
const renderFn = specialCadProcess
? cadPackages[state.ideType][specialCadProcess]
: cadPackages[state.ideType].render
return renderFn({
code: state.code,
settings: {
parameters: state.isCustomizerOpen
? parameters || state.currentParameters
: {},
camera: camera || state.camera,
viewerSize: viewerSize || state.viewerSize,
quality,
},
})
}
interface RequestRenderArgs extends RequestRenderArgsStateless {
dispatch: any
}
export const requestRender = ({ dispatch, ...rest }: RequestRenderArgs) => {
const renderPromise = requestRenderStateless(rest)
if (!renderPromise) {
return
}
renderPromise
.then(
({
objectData,
message,
status,
customizerParams,
currentParameters,
}) => {
if (status === 'error') {
dispatch({
type: 'errorRender',
payload: { message },
})
} else {
dispatch({
type: 'healthyRender',
payload: {
Irev-Dev commented 2021-10-09 20:32:28 +02:00 (Migrated from github.com)
Review

Similar to PureIdeViewer, I needed a render function that didn't automatically update state so the the PureIdeViewer's could update they're model (mostly important for OpenSCAD since it needs a new image with perspective change)
It can than be wrapped to automatically update state so functionally is the same for the normal IDE viewer.

Similar to PureIdeViewer, I needed a render function that didn't automatically update state so the the PureIdeViewer's could update they're model (mostly important for OpenSCAD since it needs a new image with perspective change) It can than be wrapped to automatically update state so functionally is the same for the normal IDE viewer.
objectData,
message,
lastRunCode: code,
customizerParams,
currentParameters,
},
})
return objectData
}
}
)
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
}