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 }) => { 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 // (for the user) if there is a conflict
const isUniqueCallback = async (seed) => const isUniqueCallback = async (seed) =>
db.part.findOne({ db.part.findOne({
@@ -83,6 +83,7 @@ export const updatePart = async ({ id, input }) => {
where: { id }, where: { id },
}) })
if (imageToDestroy) { if (imageToDestroy) {
console.log(`image destroyed, publicId: ${imageToDestroy}, partId: ${id}`)
// destroy after the db has been updated // destroy after the db has been updated
destroyImage({ publicId: imageToDestroy }) destroyImage({ publicId: imageToDestroy })
} }

View File

@@ -3,7 +3,10 @@ import CascadeController from 'src/helpers/cascadeController'
import IdeToolbar from 'src/components/IdeToolbar' import IdeToolbar from 'src/components/IdeToolbar'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { threejsViewport } from 'src/cascade/js/MainPage/CascadeState' 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: const defaultExampleCode = `// Welcome to Cascade Studio! Here are some useful functions:
// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection() // Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()
@@ -53,16 +56,22 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
isChanges={isChanges && !loading} isChanges={isChanges && !loading}
isDraft={isDraft} isDraft={isDraft}
code={code} code={code}
onSave={() => { onSave={async () => {
saveCode({ const input = {
input: {
code, code,
title: part?.title, title: part?.title,
userId: currentUser?.sub, userId: currentUser?.sub,
description: part?.description, description: part?.description,
}, }
const isFork = !canEdit
if (isFork) {
const { publicId } = await captureAndSaveViewport()
input.mainImage = publicId
}
saveCode({
input,
id: part.id, id: part.id,
isFork: !canEdit, isFork,
}) })
}} }}
onExport={(type) => threejsViewport[`saveShape${type}`]()} onExport={(type) => threejsViewport[`saveShape${type}`]()}
@@ -79,7 +88,9 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
updated: false, updated: false,
} }
// Get the canvas image as a Data URL // 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) config.imageObjectURL = window.URL.createObjectURL(config.image)
async function uploadAndUpdateImage() { async function uploadAndUpdateImage() {

View File

@@ -14,6 +14,7 @@ import { FORK_PART_MUTATION } from 'src/components/IdePartCell'
import { QUERY as UsersPartsQuery } from 'src/components/PartsOfUserCell' import { QUERY as UsersPartsQuery } from 'src/components/PartsOfUserCell'
import useUser from 'src/helpers/hooks/useUser' import useUser from 'src/helpers/hooks/useUser'
import useKeyPress from 'src/helpers/hooks/useKeyPress' import useKeyPress from 'src/helpers/hooks/useKeyPress'
import { captureAndSaveViewport } from 'src/helpers/cloudinary'
const IdeToolbar = ({ const IdeToolbar = ({
canEdit, canEdit,
@@ -62,16 +63,19 @@ const IdeToolbar = ({
setWhichPopup(null) setWhichPopup(null)
} }
const saveFork = () => const saveFork = async () => {
forkPart({ const { publicId } = await captureAndSaveViewport()
return forkPart({
variables: { variables: {
input: { input: {
userId: currentUser.sub, userId: currentUser.sub,
title, title,
code, code,
mainImage: publicId,
}, },
}, },
}) })
}
const handleSave = async () => { const handleSave = async () => {
if (isDraft && isAuthenticated) { if (isDraft && isAuthenticated) {
@@ -232,9 +236,10 @@ const IdeToolbar = ({
</div> </div>
<div className="ml-auto flex items-center"> <div className="ml-auto flex items-center">
{/* Capture Screenshot link. Should only appear if part has been saved and is editable. */} {/* Capture Screenshot link. Should only appear if part has been saved and is editable. */}
{ !isDraft && canEdit && <div> {!isDraft && canEdit && (
<div>
<button <button
onClick={async event => { onClick={async (event) => {
handleClick({ event, whichPopup: 'capture' }) handleClick({ event, whichPopup: 'capture' })
setCaptureState(await onCapture()) setCaptureState(await onCapture())
}} }}
@@ -252,38 +257,61 @@ const IdeToolbar = ({
className="material-ui-overrides transform translate-y-4" className="material-ui-overrides transform translate-y-4"
> >
<div className="text-sm p-2 text-gray-500"> <div className="text-sm p-2 text-gray-500">
{ !captureState {!captureState ? (
? 'Loading...' 'Loading...'
: <div className="grid grid-cols-2"> ) : (
<div className="rounded m-auto" style={{width: 'fit-content', overflow: 'hidden'}}> <div className="grid grid-cols-2">
<div
className="rounded m-auto"
style={{ width: 'fit-content', overflow: 'hidden' }}
>
<img src={captureState.imageObjectURL} className="w-32" /> <img src={captureState.imageObjectURL} className="w-32" />
</div> </div>
<div className="p-2 text-indigo-800"> <div className="p-2 text-indigo-800">
{ (captureState.currImage && !captureState.updated) {captureState.currImage && !captureState.updated ? (
? <button className="flex justify-center mb-4" <button
className="flex justify-center mb-4"
onClick={async () => { onClick={async () => {
const cloudinaryImg = await captureState.callback() const cloudinaryImg = await captureState.callback()
setCaptureState({...captureState, currImage: cloudinaryImg.public_id, updated: true }) setCaptureState({
}}> ...captureState,
<Svg name="refresh" className="mr-2 w-4 text-indigo-600"/> Update Part Image currImage: cloudinaryImg.public_id,
updated: true,
})
}}
>
<Svg
name="refresh"
className="mr-2 w-4 text-indigo-600"
/>{' '}
Update Part Image
</button> </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> </div>
} )}
<Button <Button
iconName="save" 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" 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 shouldAnimateHover
onClick={() => handleDownload(captureState.imageObjectURL)}> onClick={() =>
handleDownload(captureState.imageObjectURL)
}
>
Download Download
</Button> </Button>
</div> </div>
</div> </div>
} )}
</div> </div>
</Popover> </Popover>
</div> } </div>
)}
<div> <div>
<button <button
onClick={(event) => handleClick({ event, whichPopup: 'tips' })} 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 // TODO: create a tidy util for uploading to Cloudinary and returning the public ID
import axios from 'axios' 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_PRESET = 'CadHub_project_images'
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/irevdev/upload' const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/irevdev/upload'
@@ -19,3 +21,12 @@ export async function uploadToCloudinary(imgBlob) {
console.error('ERROR', 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 }
}