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:
Kurt Hutten
2021-02-27 14:21:28 +11:00
committed by GitHub
4 changed files with 117 additions and 66 deletions

View File

@@ -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 })
}

View File

@@ -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)

View File

@@ -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' })}

View File

@@ -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 }
}