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 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 { element } from 'prop-types'
|
||||||
|
import { uploadToCloudinary } 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()
|
||||||
@@ -70,6 +72,18 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => {
|
|||||||
partTitle: part?.title,
|
partTitle: part?.title,
|
||||||
image: part?.user?.image,
|
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>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const IdeToolbar = ({
|
|||||||
userNamePart,
|
userNamePart,
|
||||||
isDraft,
|
isDraft,
|
||||||
code,
|
code,
|
||||||
|
onCapture,
|
||||||
}) => {
|
}) => {
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
const [whichPopup, setWhichPopup] = useState(null)
|
const [whichPopup, setWhichPopup] = useState(null)
|
||||||
@@ -104,6 +105,10 @@ const IdeToolbar = ({
|
|||||||
setIsLoginModalOpen(true)
|
setIsLoginModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const captureScreenshot = async () => {
|
||||||
|
console.log({ forkPart, onCapture: onCapture() })
|
||||||
|
}
|
||||||
|
|
||||||
const anchorOrigin = {
|
const anchorOrigin = {
|
||||||
vertical: 'bottom',
|
vertical: 'bottom',
|
||||||
horizontal: 'center',
|
horizontal: 'center',
|
||||||
@@ -219,6 +224,31 @@ const IdeToolbar = ({
|
|||||||
</Popover>
|
</Popover>
|
||||||
</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. */}
|
||||||
|
{ !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>
|
<div>
|
||||||
<button
|
<button
|
||||||
onClick={(event) => handleClick({ event, whichPopup: 'tips' })}
|
onClick={(event) => handleClick({ event, whichPopup: 'tips' })}
|
||||||
|
|||||||
@@ -30,6 +30,29 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</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': (
|
'chevron-down': (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -23,6 +23,29 @@ class CascadeController {
|
|||||||
}
|
}
|
||||||
onInit()
|
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()
|
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