Merge pull request #210 from Irev-Dev/kurt/capture-part-image-on-first-save
Capture image on initial save
This commit was merged in pull request #210.
This commit is contained in:
@@ -51,7 +51,7 @@ export const createPart = async ({ input }) => {
|
||||
}
|
||||
|
||||
export const forkPart = async ({ input }) => {
|
||||
// Only difference between create nda clone part is that clone part will generate a unique title
|
||||
// Only difference between create and fork part is that fork part will generate a unique title
|
||||
// (for the user) if there is a conflict
|
||||
const isUniqueCallback = async (seed) =>
|
||||
db.part.findOne({
|
||||
@@ -83,6 +83,7 @@ export const updatePart = async ({ id, input }) => {
|
||||
where: { id },
|
||||
})
|
||||
if (imageToDestroy) {
|
||||
console.log(`image destroyed, publicId: ${imageToDestroy}, partId: ${id}`)
|
||||
// destroy after the db has been updated
|
||||
destroyImage({ publicId: imageToDestroy })
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import CascadeController from 'src/helpers/cascadeController'
|
||||
import IdeToolbar from 'src/components/IdeToolbar'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { threejsViewport } from 'src/cascade/js/MainPage/CascadeState'
|
||||
import { uploadToCloudinary } from 'src/helpers/cloudinary'
|
||||
import {
|
||||
uploadToCloudinary,
|
||||
captureAndSaveViewport,
|
||||
} from 'src/helpers/cloudinary'
|
||||
|
||||
const defaultExampleCode = `// Welcome to Cascade Studio! Here are some useful functions:
|
||||
// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()
|
||||
@@ -53,16 +56,22 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
|
||||
isChanges={isChanges && !loading}
|
||||
isDraft={isDraft}
|
||||
code={code}
|
||||
onSave={() => {
|
||||
onSave={async () => {
|
||||
const input = {
|
||||
code,
|
||||
title: part?.title,
|
||||
userId: currentUser?.sub,
|
||||
description: part?.description,
|
||||
}
|
||||
const isFork = !canEdit
|
||||
if (isFork) {
|
||||
const { publicId } = await captureAndSaveViewport()
|
||||
input.mainImage = publicId
|
||||
}
|
||||
saveCode({
|
||||
input: {
|
||||
code,
|
||||
title: part?.title,
|
||||
userId: currentUser?.sub,
|
||||
description: part?.description,
|
||||
},
|
||||
input,
|
||||
id: part.id,
|
||||
isFork: !canEdit,
|
||||
isFork,
|
||||
})
|
||||
}}
|
||||
onExport={(type) => threejsViewport[`saveShape${type}`]()}
|
||||
@@ -71,7 +80,7 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
|
||||
partTitle: part?.title,
|
||||
image: part?.user?.image,
|
||||
}}
|
||||
onCapture={ async () => {
|
||||
onCapture={async () => {
|
||||
const config = {
|
||||
currImage: part?.mainImage,
|
||||
callback: uploadAndUpdateImage,
|
||||
@@ -79,10 +88,12 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
|
||||
updated: false,
|
||||
}
|
||||
// Get the canvas image as a Data URL
|
||||
config.image = await CascadeController.capture(threejsViewport.environment)
|
||||
config.image = await CascadeController.capture(
|
||||
threejsViewport.environment
|
||||
)
|
||||
config.imageObjectURL = window.URL.createObjectURL(config.image)
|
||||
|
||||
async function uploadAndUpdateImage(){
|
||||
async function uploadAndUpdateImage() {
|
||||
// Upload the image to Cloudinary
|
||||
const cloudinaryImgURL = await uploadToCloudinary(config.image)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { FORK_PART_MUTATION } from 'src/components/IdePartCell'
|
||||
import { QUERY as UsersPartsQuery } from 'src/components/PartsOfUserCell'
|
||||
import useUser from 'src/helpers/hooks/useUser'
|
||||
import useKeyPress from 'src/helpers/hooks/useKeyPress'
|
||||
import { captureAndSaveViewport } from 'src/helpers/cloudinary'
|
||||
|
||||
const IdeToolbar = ({
|
||||
canEdit,
|
||||
@@ -62,16 +63,19 @@ const IdeToolbar = ({
|
||||
setWhichPopup(null)
|
||||
}
|
||||
|
||||
const saveFork = () =>
|
||||
forkPart({
|
||||
const saveFork = async () => {
|
||||
const { publicId } = await captureAndSaveViewport()
|
||||
return forkPart({
|
||||
variables: {
|
||||
input: {
|
||||
userId: currentUser.sub,
|
||||
title,
|
||||
code,
|
||||
mainImage: publicId,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (isDraft && isAuthenticated) {
|
||||
@@ -109,9 +113,9 @@ const IdeToolbar = ({
|
||||
const handleDownload = (url) => {
|
||||
const aTag = document.createElement('a')
|
||||
document.body.appendChild(aTag)
|
||||
aTag.href= url
|
||||
aTag.href = url
|
||||
aTag.style.display = 'none'
|
||||
aTag.download = `CadHub_${ Date.now() }.jpg`
|
||||
aTag.download = `CadHub_${Date.now()}.jpg`
|
||||
aTag.click()
|
||||
document.body.removeChild(aTag)
|
||||
}
|
||||
@@ -232,58 +236,82 @@ const IdeToolbar = ({
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
{/* Capture Screenshot link. Should only appear if part has been saved and is editable. */}
|
||||
{ !isDraft && canEdit && <div>
|
||||
<button
|
||||
onClick={async event => {
|
||||
handleClick({ event, whichPopup: 'capture' })
|
||||
setCaptureState(await onCapture())
|
||||
}}
|
||||
className="text-indigo-300 flex items-center pr-6"
|
||||
>
|
||||
Save Part Image <Svg name="camera" className="pl-2 w-8" />
|
||||
</button>
|
||||
<Popover
|
||||
id={id}
|
||||
open={whichPopup === 'capture'}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={anchorOrigin}
|
||||
transformOrigin={transformOrigin}
|
||||
className="material-ui-overrides transform translate-y-4"
|
||||
>
|
||||
<div className="text-sm p-2 text-gray-500">
|
||||
{ !captureState
|
||||
? 'Loading...'
|
||||
: <div className="grid grid-cols-2">
|
||||
<div className="rounded m-auto" style={{width: 'fit-content', overflow: 'hidden'}}>
|
||||
<img src={ captureState.imageObjectURL } className="w-32" />
|
||||
</div>
|
||||
<div className="p-2 text-indigo-800">
|
||||
{ (captureState.currImage && !captureState.updated)
|
||||
? <button className="flex justify-center mb-4"
|
||||
onClick={ async () => {
|
||||
{!isDraft && canEdit && (
|
||||
<div>
|
||||
<button
|
||||
onClick={async (event) => {
|
||||
handleClick({ event, whichPopup: 'capture' })
|
||||
setCaptureState(await onCapture())
|
||||
}}
|
||||
className="text-indigo-300 flex items-center pr-6"
|
||||
>
|
||||
Save Part Image <Svg name="camera" className="pl-2 w-8" />
|
||||
</button>
|
||||
<Popover
|
||||
id={id}
|
||||
open={whichPopup === 'capture'}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={anchorOrigin}
|
||||
transformOrigin={transformOrigin}
|
||||
className="material-ui-overrides transform translate-y-4"
|
||||
>
|
||||
<div className="text-sm p-2 text-gray-500">
|
||||
{!captureState ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<div className="grid grid-cols-2">
|
||||
<div
|
||||
className="rounded m-auto"
|
||||
style={{ width: 'fit-content', overflow: 'hidden' }}
|
||||
>
|
||||
<img src={captureState.imageObjectURL} className="w-32" />
|
||||
</div>
|
||||
<div className="p-2 text-indigo-800">
|
||||
{captureState.currImage && !captureState.updated ? (
|
||||
<button
|
||||
className="flex justify-center mb-4"
|
||||
onClick={async () => {
|
||||
const cloudinaryImg = await captureState.callback()
|
||||
setCaptureState({...captureState, currImage: cloudinaryImg.public_id, updated: true })
|
||||
}}>
|
||||
<Svg name="refresh" className="mr-2 w-4 text-indigo-600"/> Update Part Image
|
||||
setCaptureState({
|
||||
...captureState,
|
||||
currImage: cloudinaryImg.public_id,
|
||||
updated: true,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Svg
|
||||
name="refresh"
|
||||
className="mr-2 w-4 text-indigo-600"
|
||||
/>{' '}
|
||||
Update Part Image
|
||||
</button>
|
||||
: <div className="flex justify-center mb-4">
|
||||
<Svg name="checkmark" className="mr-2 w-6 text-indigo-600"/> Part Image Updated
|
||||
) : (
|
||||
<div className="flex justify-center mb-4">
|
||||
<Svg
|
||||
name="checkmark"
|
||||
className="mr-2 w-6 text-indigo-600"
|
||||
/>{' '}
|
||||
Part Image Updated
|
||||
</div>
|
||||
}
|
||||
<Button
|
||||
iconName="save"
|
||||
className="shadow-md hover:shadow-lg border-indigo-600 border-2 border-opacity-0 hover:border-opacity-100 bg-indigo-800 text-indigo-100 text-opacity-100 bg-opacity-80"
|
||||
shouldAnimateHover
|
||||
onClick={() => handleDownload(captureState.imageObjectURL)}>
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
iconName="save"
|
||||
className="shadow-md hover:shadow-lg border-indigo-600 border-2 border-opacity-0 hover:border-opacity-100 bg-indigo-800 text-indigo-100 text-opacity-100 bg-opacity-80"
|
||||
shouldAnimateHover
|
||||
onClick={() =>
|
||||
handleDownload(captureState.imageObjectURL)
|
||||
}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Popover>
|
||||
</div> }
|
||||
)}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<button
|
||||
onClick={(event) => handleClick({ event, whichPopup: 'tips' })}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// TODO: create a tidy util for uploading to Cloudinary and returning the public ID
|
||||
import axios from 'axios'
|
||||
import { threejsViewport } from 'src/cascade/js/MainPage/CascadeState'
|
||||
import CascadeController from 'src/helpers/cascadeController'
|
||||
|
||||
const CLOUDINARY_UPLOAD_PRESET = 'CadHub_project_images'
|
||||
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/irevdev/upload'
|
||||
@@ -18,4 +20,13 @@ export async function uploadToCloudinary(imgBlob) {
|
||||
} catch (e) {
|
||||
console.error('ERROR', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const captureAndSaveViewport = async () => {
|
||||
// Get the canvas image as a Data URL
|
||||
const imgBlob = await CascadeController.capture(threejsViewport.environment)
|
||||
|
||||
// Upload the image to Cloudinary
|
||||
const { public_id: publicId } = await uploadToCloudinary(imgBlob)
|
||||
return { publicId, imgBlob }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user