From 6e6425735f68f0ff5a70158225f548f02b3319bb Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 25 Feb 2021 01:50:03 -0500 Subject: [PATCH 1/6] Began building out screenshot capture feature. --- .../IdeCascadeStudio/IdeCascadeStudio.js | 14 +++++++++ web/src/components/IdeToolbar/IdeToolbar.js | 30 +++++++++++++++++++ web/src/components/Svg/Svg.js | 23 ++++++++++++++ web/src/helpers/cascadeController.js | 23 ++++++++++++++ web/src/helpers/cloudinary.js | 23 ++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 web/src/helpers/cloudinary.js diff --git a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js index 72cf624..f911887 100644 --- a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js +++ b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js @@ -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 }) + }} /> diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index ba5307a..73f70c1 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -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 = ({
+ {/* Capture Screenshot link. Should only appear if part has been saved and is editable. */} + { !isDraft && canEdit &&
+ + +
+ Saving... +
+
+
}
diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index 73f70c1..4f8ab54 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -31,6 +31,7 @@ const IdeToolbar = ({ const { isAuthenticated, currentUser } = useAuth() const showForkButton = !(canEdit || isDraft) const [title, setTitle] = useState('untitled-part') + const [captureState, setCaptureState] = useState(false) const { user } = useUser() useKeyPress((e) => { const rx = /INPUT|SELECT|TEXTAREA/i @@ -106,7 +107,18 @@ const IdeToolbar = ({ } const captureScreenshot = async () => { - console.log({ forkPart, onCapture: onCapture() }) + setCaptureState(await onCapture()) + // console.log({ onCapture: onCapture() }) + } + + const handleDownload = (imgBlob) => { + const aTag = document.createElement('a') + document.body.appendChild(aTag) + const url = URL.createObjectURL(imgBlob) + aTag.href= url + aTag.style.display = 'none' + aTag.download = `CodeCad_${ Date.now() }.jpg` + aTag.click() } const anchorOrigin = { @@ -227,9 +239,9 @@ const IdeToolbar = ({ {/* Capture Screenshot link. Should only appear if part has been saved and is editable. */} { !isDraft && canEdit &&
+
+
+ } } diff --git a/web/src/components/Svg/Svg.js b/web/src/components/Svg/Svg.js index 0636b53..4456a9b 100644 --- a/web/src/components/Svg/Svg.js +++ b/web/src/components/Svg/Svg.js @@ -53,6 +53,17 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => { stroke-linecap="round"/> ), + 'checkmark': ( + + + ), 'chevron-down': ( Date: Thu, 25 Feb 2021 04:52:01 -0500 Subject: [PATCH 3/6] Added ability to update part image using captured screenshot --- checkmark.svg | 3 -- refresh.svg | 3 ++ .../IdeCascadeStudio/IdeCascadeStudio.js | 39 +++++++++---------- web/src/components/IdeToolbar/IdeToolbar.js | 30 ++++++++------ web/src/components/Svg/Svg.js | 12 ++++++ 5 files changed, 52 insertions(+), 35 deletions(-) delete mode 100644 checkmark.svg create mode 100644 refresh.svg diff --git a/checkmark.svg b/checkmark.svg deleted file mode 100644 index 5d4c32b..0000000 --- a/checkmark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/refresh.svg b/refresh.svg new file mode 100644 index 0000000..c4c702b --- /dev/null +++ b/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js index a85bc22..e7e37e8 100644 --- a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js +++ b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js @@ -76,28 +76,27 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => { // Get the canvas image as a Data URL const imgBlob = await CascadeController.capture(threejsViewport.environment) - // // Upload the image to Cloudinary - // const cloudinaryImg = await uploadToCloudinary(imgBlob) + async function uploadAndUpdateImage(){ + // Upload the image to Cloudinary + const cloudinaryImg = await 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 - // if (!part.mainImage) { - // saveCode({ - // input: { - // code, - // title: part?.title, - // userId: currentUser?.sub, - // description: part?.description, - // mainImage: cloudinaryImg.public_id, - // }, - // id: part.id, - // isFork: !canEdit, - // }) - // } else { - // console.log('not saving, passing back into IDE', cloudinaryImg) - // } + // Save the screenshot as the mainImage + saveCode({ + input: { + code, + title: part?.title, + userId: currentUser?.sub, + description: part?.description, + mainImage: cloudinaryImg.public_id, + }, + id: part.id, + isFork: !canEdit, + }) - return imgBlob + return cloudinaryImg + } + + return { image: imgBlob, mainImage: part.mainImage, callback: uploadAndUpdateImage} }} /> diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index 4f8ab54..acfb543 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -106,11 +106,6 @@ const IdeToolbar = ({ setIsLoginModalOpen(true) } - const captureScreenshot = async () => { - setCaptureState(await onCapture()) - // console.log({ onCapture: onCapture() }) - } - const handleDownload = (imgBlob) => { const aTag = document.createElement('a') document.body.appendChild(aTag) @@ -260,16 +255,27 @@ const IdeToolbar = ({ { !captureState ? 'Loading...' :
- -
-
- Part Image Set -
+
+ +
+
+ { (captureState.mainImage && !captureState.updated) + ? + :
+ Part Image Set +
+ }
diff --git a/web/src/components/Svg/Svg.js b/web/src/components/Svg/Svg.js index 4456a9b..20d8769 100644 --- a/web/src/components/Svg/Svg.js +++ b/web/src/components/Svg/Svg.js @@ -311,6 +311,18 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => { /> ), + refresh: ( + + + + ), save: ( Date: Thu, 25 Feb 2021 20:14:44 -0500 Subject: [PATCH 4/6] Updated screenshot functionality to focus on Set Part Image per Figma feedback --- .../IdeCascadeStudio/IdeCascadeStudio.js | 34 ++++++++----------- web/src/components/IdeToolbar/IdeToolbar.js | 23 ++++--------- web/src/components/Svg/Svg.js | 18 +++++----- 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js index e7e37e8..62623e9 100644 --- a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js +++ b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js @@ -76,27 +76,23 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => { // Get the canvas image as a Data URL const imgBlob = await CascadeController.capture(threejsViewport.environment) - async function uploadAndUpdateImage(){ - // Upload the image to Cloudinary - const cloudinaryImg = await uploadToCloudinary(imgBlob) + // Upload the image to Cloudinary + const cloudinaryImg = await uploadToCloudinary(imgBlob) - // Save the screenshot as the mainImage - saveCode({ - input: { - code, - title: part?.title, - userId: currentUser?.sub, - description: part?.description, - mainImage: cloudinaryImg.public_id, - }, - id: part.id, - isFork: !canEdit, - }) + // Save the screenshot as the mainImage + saveCode({ + input: { + code, + title: part?.title, + userId: currentUser?.sub, + description: part?.description, + mainImage: cloudinaryImg.public_id, + }, + id: part?.id, + isFork: !canEdit, + }) - return cloudinaryImg - } - - return { image: imgBlob, mainImage: part.mainImage, callback: uploadAndUpdateImage} + return { imgBlob, cloudinaryImg } }} />
diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index acfb543..efff79e 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -112,7 +112,7 @@ const IdeToolbar = ({ const url = URL.createObjectURL(imgBlob) aTag.href= url aTag.style.display = 'none' - aTag.download = `CodeCad_${ Date.now() }.jpg` + aTag.download = `CadHub_${ Date.now() }.jpg` aTag.click() } @@ -240,7 +240,7 @@ const IdeToolbar = ({ }} className="text-indigo-300 flex items-center pr-6" > - Capture + Set Part Image
- +
- { (captureState.mainImage && !captureState.updated) - ? - :
- Part Image Set -
- } +
+ Part Image Set +
diff --git a/web/src/components/Svg/Svg.js b/web/src/components/Svg/Svg.js index 20d8769..47bbb76 100644 --- a/web/src/components/Svg/Svg.js +++ b/web/src/components/Svg/Svg.js @@ -38,19 +38,19 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => { + strokeWidth="2" + strokeLinecap="round"/> + strokeWidth="2"/> + strokeWidth="2" + strokeLinecap="round"/> ), 'checkmark': ( @@ -59,8 +59,8 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => { viewBox="0 0 21 20" fill="none"> ), @@ -319,8 +319,8 @@ const Svg = ({ name, className: className2, strokeWidth = 2 }) => { + strokeWidth="2" + strokeLinecap="round"/> ), save: ( From 7fab53d200efb509ef950092ae566d3c00e8b218 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 25 Feb 2021 21:26:25 -0500 Subject: [PATCH 5/6] removed unused code --- refresh.svg | 3 --- web/src/components/IdeCascadeStudio/IdeCascadeStudio.js | 5 ----- web/src/components/IdeToolbar/IdeToolbar.js | 1 + web/src/helpers/cascadeController.js | 2 +- 4 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 refresh.svg diff --git a/refresh.svg b/refresh.svg deleted file mode 100644 index c4c702b..0000000 --- a/refresh.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js index 62623e9..63ffdcc 100644 --- a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js +++ b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js @@ -3,7 +3,6 @@ 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: @@ -82,10 +81,6 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => { // Save the screenshot as the mainImage saveCode({ input: { - code, - title: part?.title, - userId: currentUser?.sub, - description: part?.description, mainImage: cloudinaryImg.public_id, }, id: part?.id, diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index efff79e..deb9313 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -114,6 +114,7 @@ const IdeToolbar = ({ aTag.style.display = 'none' aTag.download = `CadHub_${ Date.now() }.jpg` aTag.click() + document.body.removeChild(aTag) } const anchorOrigin = { diff --git a/web/src/helpers/cascadeController.js b/web/src/helpers/cascadeController.js index ba38e00..e5ba11a 100644 --- a/web/src/helpers/cascadeController.js +++ b/web/src/helpers/cascadeController.js @@ -25,7 +25,7 @@ class CascadeController { } capture(environment) { - let width = 512, height = 384; + let width = 512, height = 384; // These can be adjusted to the best size for the Part Profile. environment.camera.aspect = width / height; environment.camera.updateProjectionMatrix(); environment.renderer.setSize(width, height); From 3cae431e61ace3d42744bf4038eeaf3b033a31f9 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Fri, 26 Feb 2021 20:02:52 -0500 Subject: [PATCH 6/6] Reverted to screenshot confirmation flow, fixed layout bug, updated language to Save --- .../IdeCascadeStudio/IdeCascadeStudio.js | 41 +++++++++++++------ web/src/components/IdePartCell/IdePartCell.js | 2 +- web/src/components/IdeToolbar/IdeToolbar.js | 26 ++++++++---- web/src/helpers/cascadeController.js | 3 +- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js index 63ffdcc..42c75d1 100644 --- a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js +++ b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js @@ -72,22 +72,39 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => { image: part?.user?.image, }} onCapture={ async () => { + const config = { + currImage: part?.mainImage, + callback: uploadAndUpdateImage, + cloudinaryImgURL: '', + updated: false, + } // Get the canvas image as a Data URL - const imgBlob = await CascadeController.capture(threejsViewport.environment) + config.image = await CascadeController.capture(threejsViewport.environment) + config.imageObjectURL = window.URL.createObjectURL(config.image) - // Upload the image to Cloudinary - const cloudinaryImg = await uploadToCloudinary(imgBlob) + async function uploadAndUpdateImage(){ + // Upload the image to Cloudinary + const cloudinaryImgURL = await uploadToCloudinary(config.image) - // Save the screenshot as the mainImage - saveCode({ - input: { - mainImage: cloudinaryImg.public_id, - }, - id: part?.id, - isFork: !canEdit, - }) + // Save the screenshot as the mainImage + saveCode({ + input: { + mainImage: cloudinaryImgURL.public_id, + }, + id: part?.id, + isFork: !canEdit, + }) - return { imgBlob, cloudinaryImg } + return cloudinaryImgURL + } + + // if there isn't a screenshot saved yet, just go ahead and save right away + if (!part || !part.mainImage) { + config.cloudinaryImgURL = await uploadAndUpdateImage().public_id + config.updated = true + } + + return config }} />
diff --git a/web/src/components/IdePartCell/IdePartCell.js b/web/src/components/IdePartCell/IdePartCell.js index 581a032..7a2975d 100644 --- a/web/src/components/IdePartCell/IdePartCell.js +++ b/web/src/components/IdePartCell/IdePartCell.js @@ -50,7 +50,7 @@ export const Success = ({ part, refetch }) => { const { user } = useUser() const [updatePart, { loading, error }] = useMutation(UPDATE_PART_MUTATION, { onCompleted: () => { - addMessage('Part updated.', { classes: 'rw-flash-success' }) + addMessage('Part updated.', { classes: 'rw-flash-success fixed w-screen z-10' }) }, }) const [forkPart] = useMutation(FORK_PART_MUTATION, { diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index deb9313..337b863 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -106,10 +106,9 @@ const IdeToolbar = ({ setIsLoginModalOpen(true) } - const handleDownload = (imgBlob) => { + const handleDownload = (url) => { const aTag = document.createElement('a') document.body.appendChild(aTag) - const url = URL.createObjectURL(imgBlob) aTag.href= url aTag.style.display = 'none' aTag.download = `CadHub_${ Date.now() }.jpg` @@ -241,7 +240,7 @@ const IdeToolbar = ({ }} className="text-indigo-300 flex items-center pr-6" > - Set Part Image + Save Part Image
- +
-
- Part Image Set -
+ { (captureState.currImage && !captureState.updated) + ? + :
+ Part Image Updated +
+ }
diff --git a/web/src/helpers/cascadeController.js b/web/src/helpers/cascadeController.js index e5ba11a..669c374 100644 --- a/web/src/helpers/cascadeController.js +++ b/web/src/helpers/cascadeController.js @@ -24,8 +24,7 @@ class CascadeController { onInit() } - capture(environment) { - let width = 512, height = 384; // These can be adjusted to the best size for the Part Profile. + capture(environment, width = 512, height = 384) { environment.camera.aspect = width / height; environment.camera.updateProjectionMatrix(); environment.renderer.setSize(width, height);