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} + + + + { + navigate(routes.part(userNamePart)) + }} + > + Part Profile + + > + )} { - navigate(routes.part(userNamePart)) - }} - > - Part Profile - - - {canEdit ? 'Save' : 'Fork'} - {isChanges && ( + {showForkButton ? 'Fork' : 'Save'} + {isChanges && !isDraft && ( * diff --git a/web/src/components/PartCell/PartCell.js b/web/src/components/PartCell/PartCell.js index a35ccd7..5a4a55b 100644 --- a/web/src/components/PartCell/PartCell.js +++ b/web/src/components/PartCell/PartCell.js @@ -117,7 +117,7 @@ export const Success = ({ userPart, variables: { isEditable }, refetch }) => { addMessage('Part updated.', { classes: 'rw-flash-success' }) }, }) - const [createUser] = useMutation(CREATE_PART_MUTATION, { + const [createPart] = useMutation(CREATE_PART_MUTATION, { onCompleted: ({ createPart }) => { navigate( routes.part({ @@ -130,7 +130,7 @@ export const Success = ({ userPart, variables: { isEditable }, refetch }) => { }) const onSave = (id, input) => { if (!id) { - createUser({ variables: { input } }) + createPart({ variables: { input } }) return } updateUser({ variables: { id, input } }) diff --git a/web/src/layouts/MainLayout/MainLayout.js b/web/src/layouts/MainLayout/MainLayout.js index 257cdb7..48db716 100644 --- a/web/src/layouts/MainLayout/MainLayout.js +++ b/web/src/layouts/MainLayout/MainLayout.js @@ -137,20 +137,12 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => { - {isAuthenticated && data?.user?.userName ? ( - - - - ) : ( - - )} + + + {isAuthenticated ? ( { + return ( + + + + + ) +} + +export default DraftPartPage diff --git a/web/src/pages/DraftPartPage/DraftPartPage.stories.js b/web/src/pages/DraftPartPage/DraftPartPage.stories.js new file mode 100644 index 0000000..2d4c7a3 --- /dev/null +++ b/web/src/pages/DraftPartPage/DraftPartPage.stories.js @@ -0,0 +1,7 @@ +import DraftPartPage from './DraftPartPage' + +export const generated = () => { + return +} + +export default { title: 'Pages/DraftPartPage' } diff --git a/web/src/pages/DraftPartPage/DraftPartPage.test.js b/web/src/pages/DraftPartPage/DraftPartPage.test.js new file mode 100644 index 0000000..89d1c58 --- /dev/null +++ b/web/src/pages/DraftPartPage/DraftPartPage.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import DraftPartPage from './DraftPartPage' + +describe('DraftPartPage', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +})