Began building out screenshot capture feature.
This commit is contained in:
@@ -3,6 +3,8 @@ 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 { element } from 'prop-types'
|
||||
import { uploadToCloudinary } from 'src/helpers/cloudinary'
|
||||
|
||||
const defaultExampleCode = `// Welcome to Cascade Studio! Here are some useful functions:
|
||||
// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()
|
||||
@@ -70,6 +72,18 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
|
||||
partTitle: part?.title,
|
||||
image: part?.user?.image,
|
||||
}}
|
||||
onCapture={ async () => {
|
||||
// Get the canvas image as a Data URL
|
||||
const imgBlob = await CascadeController.capture(threejsViewport.environment)
|
||||
const imgURL = window.URL.createObjectURL(imgBlob)
|
||||
|
||||
// TODO: Upload the image to Cloudinary
|
||||
// uploadToCloudinary(imgBlob)
|
||||
|
||||
// TODO: Save the screenshot as the mainImage if none has been set
|
||||
// If it has been set, pass along the Blob without uploading
|
||||
// onSave(part?.id, { ...input, mainImage: cloudinaryPublicId })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -23,6 +23,7 @@ const IdeToolbar = ({
|
||||
userNamePart,
|
||||
isDraft,
|
||||
code,
|
||||
onCapture,
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const [whichPopup, setWhichPopup] = useState(null)
|
||||
@@ -104,6 +105,10 @@ const IdeToolbar = ({
|
||||
setIsLoginModalOpen(true)
|
||||
}
|
||||
|
||||
const captureScreenshot = async () => {
|
||||
console.log({ forkPart, onCapture: onCapture() })
|
||||
}
|
||||
|
||||
const anchorOrigin = {
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
@@ -219,6 +224,31 @@ const IdeToolbar = ({
|
||||
</Popover>
|
||||
</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={(event) => {
|
||||
captureScreenshot()
|
||||
handleClick({ event, whichPopup: 'capture' })
|
||||
}}
|
||||
className="text-indigo-300 flex items-center pr-6"
|
||||
>
|
||||
Capture <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">
|
||||
Saving...
|
||||
</div>
|
||||
</Popover>
|
||||
</div> }
|
||||
<div>
|
||||
<button
|
||||
onClick={(event) => handleClick({ event, whichPopup: 'tips' })}
|
||||
|
||||
@@ -30,6 +30,29 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
'camera': (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 21">
|
||||
<path
|
||||
d="M6 5H4C2.34315 5 1 6.34315 1 8V17C1 18.6569 2.34315 20 4 20H20C21.6569 20 23 18.6569 23 17V8C23 6.34315 21.6569 5 20 5H18"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="11"
|
||||
r="5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"/>
|
||||
<path
|
||||
d="M16 2.68641C14.8716 1.61443 13.5582 1 12.1563 1C10.6229 1 9.19532 1.7351 8 3"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"/>
|
||||
</svg>
|
||||
),
|
||||
'chevron-down': (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -23,6 +23,29 @@ class CascadeController {
|
||||
}
|
||||
onInit()
|
||||
}
|
||||
|
||||
capture(environment) {
|
||||
let width = 512, height = 384;
|
||||
environment.camera.aspect = width / height;
|
||||
environment.camera.updateProjectionMatrix();
|
||||
environment.renderer.setSize(width, height);
|
||||
environment.renderer.render(environment.scene, environment.camera, null, false);
|
||||
let imgBlob = new Promise((resolve, reject) => {
|
||||
environment.renderer.domElement.toBlob(
|
||||
(blob) => {
|
||||
blob.name = `part_capture-${ Date.now() }`
|
||||
resolve(blob)
|
||||
},
|
||||
'image/jpeg',
|
||||
1
|
||||
);
|
||||
})
|
||||
|
||||
// Return to original dimensions
|
||||
environment.onWindowResize();
|
||||
|
||||
return imgBlob
|
||||
}
|
||||
}
|
||||
|
||||
export default new CascadeController()
|
||||
|
||||
23
web/src/helpers/cloudinary.js
Normal file
23
web/src/helpers/cloudinary.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// TODO: create a tidy util for uploading to Cloudinary and returning the public ID
|
||||
import axios from 'axios'
|
||||
|
||||
const CLOUDINARY_UPLOAD_PRESET = 'CadHub_project_images'
|
||||
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/irevdev/upload'
|
||||
|
||||
export async function uploadToCloudinary(imgBlob) {
|
||||
// const imageData = new FormData()
|
||||
// imageData.append('upload_preset', CLOUDINARY_UPLOAD_PRESET)
|
||||
// imageData.append('file', croppedFile)
|
||||
// let upload = axios.post(CLOUDINARY_UPLOAD_URL, imageData)
|
||||
|
||||
// try {
|
||||
// const { data } = await upload
|
||||
// if (data && data.public_id !== '') {
|
||||
// onImageUpload({ cloudinaryPublicId: data.public_id })
|
||||
// setCloudinaryId(data.public_id)
|
||||
// setIsModalOpen(false)
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error('ERROR', e)
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user