Put social media save popover into editor tab (#541)

and make them live
This commit was merged in pull request #541.
This commit is contained in:
Kurt Hutten
2021-10-12 06:09:56 +11:00
committed by GitHub
parent 6c093e65bf
commit 4804c3bfe9
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"
>
<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()
})
toast.promise(uploadPromise, {
loading: 'Saving Image',
success: (finalImg: string) => (
<div className="flex flex-col items-center">
<b className="py-2">Image saved!</b>
<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'
}`}
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
}
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: {
objectData,
message,
lastRunCode: code,
customizerParams,
currentParameters,
},
})
return objectData
}
}
)
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
}