From c84dcda4a1f8b610bb8f8a31fd484b572fd20b92 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Tue, 29 Dec 2020 18:53:49 +1100 Subject: [PATCH] Issue-178 Add draft mode for IDE resolves #178 Initially the UI forced users to create a "part" before they got access to the ide, now we're letting users go straight to hacking in the ide and saving can come later. Better at getting users to the code earlier --- web/src/Routes.js | 1 + .../IdeCascadeStudio/IdeCascadeStudio.js | 37 +++++-- web/src/components/IdePartCell/IdePartCell.js | 6 +- web/src/components/IdeToolbar/IdeToolbar.js | 104 ++++++++++++------ web/src/components/PartCell/PartCell.js | 4 +- web/src/layouts/MainLayout/MainLayout.js | 16 +-- web/src/pages/DraftPartPage/DraftPartPage.js | 18 +++ .../DraftPartPage/DraftPartPage.stories.js | 7 ++ .../pages/DraftPartPage/DraftPartPage.test.js | 11 ++ 9 files changed, 148 insertions(+), 56 deletions(-) create mode 100644 web/src/pages/DraftPartPage/DraftPartPage.js create mode 100644 web/src/pages/DraftPartPage/DraftPartPage.stories.js create mode 100644 web/src/pages/DraftPartPage/DraftPartPage.test.js diff --git a/web/src/Routes.js b/web/src/Routes.js index eeadbf6..1528bd1 100644 --- a/web/src/Routes.js +++ b/web/src/Routes.js @@ -47,6 +47,7 @@ const Routes = () => { {/* End ownership enforced routes */} + diff --git a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js index 59bd46a..f97c1c9 100644 --- a/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js +++ b/web/src/components/IdeCascadeStudio/IdeCascadeStudio.js @@ -1,26 +1,47 @@ import { useAuth } from '@redwoodjs/auth' -import { Link, routes } from '@redwoodjs/router' import CascadeController from 'src/helpers/cascadeController' import IdeToolbar from 'src/components/IdeToolbar' import { useEffect, useState } from 'react' +const defaultExampleCode = `// Welcome to Cascade Studio! Here are some useful functions: +// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection() +// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon() +// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(), +// FilletEdges(), ChamferEdges(), +// Slider(), Button(), Checkbox() + +let holeRadius = Slider("Radius", 30 , 20 , 40); + +let sphere = Sphere(50); +let cylinderZ = Cylinder(holeRadius, 200, true); +let cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true)); +let cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true)); + +Translate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ])); + +Translate([-130, 0, 100], Text3D("Start Hacking")); + +// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!` + const IdeCascadeStudio = ({ part, saveCode, loading }) => { - const [code, setCode] = useState(part.code) + const isDraft = !part + const [code, setCode] = useState(isDraft ? defaultExampleCode : part.code) const { currentUser } = useAuth() const canEdit = currentUser?.sub === part?.user?.id useEffect(() => { // Cascade studio attaches "cascade-container" a div outside the react app in 'web/src/index.html', and so we are // "opening" and "closing" it for the ide part of the app by displaying none or block. Which is why this useEffect // returns a clean up function that hides the div again. + setCode(part?.code || '') const onCodeChange = (code) => setCode(code) - CascadeController.initialise(onCodeChange, part.code) + CascadeController.initialise(onCodeChange, code || '') const element = document.getElementById('cascade-container') element.setAttribute('style', 'display: block; opacity: 100%; overflow: hidden; height: calc(100vh - 8rem)') // eslint-disable-line return () => { element.setAttribute('style', 'display: none; overflow: hidden; height: calc(100vh - 8rem)') // eslint-disable-line } - }, [part.code]) - const isChanges = code !== part.code + }, [part?.code]) + const isChanges = code !== part?.code return ( <> @@ -28,6 +49,8 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => { { saveCode({ input: { @@ -42,8 +65,8 @@ const IdeCascadeStudio = ({ part, saveCode, loading }) => { }} onExport={(type) => threejsViewport[`saveShape${type}`]()} userNamePart={{ - userName: part.user.userName, - partTitle: part.title, + userName: part?.user?.userName, + partTitle: part?.title, image: part?.user?.image, }} /> diff --git a/web/src/components/IdePartCell/IdePartCell.js b/web/src/components/IdePartCell/IdePartCell.js index 6bbffcf..bc91be8 100644 --- a/web/src/components/IdePartCell/IdePartCell.js +++ b/web/src/components/IdePartCell/IdePartCell.js @@ -27,7 +27,7 @@ const UPDATE_PART_MUTATION = gql` } } ` -const FORK_PART_MUTATION = gql` +export const FORK_PART_MUTATION = gql` mutation ForkPartMutation($input: CreatePartInput!) { forkPart(input: $input) { id @@ -62,9 +62,9 @@ export const Success = ({ part, refetch }) => { }, }) - const saveCode = ({ input, id, isFork }) => { + const saveCode = async ({ input, id, isFork }) => { if (!isFork) { - updatePart({ variables: { id, input } }) + await updatePart({ variables: { id, input } }) refetch() return } diff --git a/web/src/components/IdeToolbar/IdeToolbar.js b/web/src/components/IdeToolbar/IdeToolbar.js index 8c5d452..c9fd12c 100644 --- a/web/src/components/IdeToolbar/IdeToolbar.js +++ b/web/src/components/IdeToolbar/IdeToolbar.js @@ -4,17 +4,43 @@ import OutBound from 'src/components/OutBound' import ReactGA from 'react-ga' import { Link, routes, navigate } from '@redwoodjs/router' import { useAuth } from '@redwoodjs/auth' +import { useMutation, useFlash } from '@redwoodjs/web' import Button from 'src/components/Button' import ImageUploader from 'src/components/ImageUploader' import Svg from '../Svg/Svg' -import LoginModal from '../LoginModal/LoginModal' +import LoginModal from 'src/components/LoginModal' +import { FORK_PART_MUTATION } from 'src/components/IdePartCell' -const IdeToolbar = ({ canEdit, isChanges, onSave, onExport, userNamePart }) => { +const IdeToolbar = ({ + canEdit, + isChanges, + onSave, + onExport, + userNamePart, + isDraft, + code, +}) => { const [anchorEl, setAnchorEl] = useState(null) const [whichPopup, setWhichPopup] = useState(null) const [isLoginModalOpen, setIsLoginModalOpen] = useState(false) - const { isAuthenticated } = useAuth() + const { isAuthenticated, currentUser } = useAuth() + const showForkButton = !(canEdit || isDraft) + + const { addMessage } = useFlash() + const [forkPart] = useMutation(FORK_PART_MUTATION, { + onCompleted: ({ forkPart }) => { + navigate( + routes.ide({ + userName: forkPart?.user?.userName, + partTitle: forkPart?.title, + }) + ) + addMessage(`Part created with title: ${forkPart?.title}.`, { + classes: 'rw-flash-success', + }) + }, + }) const handleClick = ({ event, whichPopup }) => { setAnchorEl(event.currentTarget) @@ -27,7 +53,17 @@ const IdeToolbar = ({ canEdit, isChanges, onSave, onExport, userNamePart }) => { } const handleSave = () => { - if (isAuthenticated) onSave() + if (isDraft && isAuthenticated) + forkPart({ + variables: { + input: { + userId: currentUser.sub, + title: 'draft', + code, + }, + }, + }) + else if (isAuthenticated) onSave() else recordedLogin() } @@ -55,39 +91,43 @@ const IdeToolbar = ({ canEdit, isChanges, onSave, onExport, userNamePart }) => { id="cadhub-ide-toolbar" className="flex bg-gradient-to-r from-gray-900 to-indigo-900 pt-1" > -
-
- -
-
- - {userNamePart?.userName} - -
-
+ {!isDraft && ( + <> +
+
+ +
+
+ + {userNamePart?.userName} + +
+
+ + + )} -