├── .editorconfig ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .env.defaults ├── .env.example ├── .eslintignore ├── .gitignore ├── .nvmrc ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json ├── api │ ├── db │ │ ├── dataMigrations │ │ │ └── .keep │ │ ├── migrations │ │ │ ├── 20210228081443_initial_migration_to_postgres_from_sqlite │ │ │ │ └── migration.sql │ │ │ ├── 20210704054715_create_data_migrations │ │ │ │ └── migration.sql │ │ │ ├── 20210708100818_drop_cascade_tables │ │ │ │ └── migration.sql │ │ │ ├── 20210709103029_add_tables_back_in │ │ │ │ └── migration.sql │ │ │ ├── 20210716102203_add_cad_package_to_projects │ │ │ │ └── migration.sql │ │ │ ├── 20210812210054_add_social_image │ │ │ │ └── migration.sql │ │ │ ├── 20210815062510_make_social_card_one_to_one_with_project │ │ │ │ └── migration.sql │ │ │ ├── 20210912041733_add_jscad │ │ │ │ └── migration.sql │ │ │ ├── 20210925001652_add_project_forking │ │ │ │ └── migration.sql │ │ │ ├── 20211113002346_prisma_v3 │ │ │ │ └── migration.sql │ │ │ ├── 20211129205924_curv │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ ├── jest.config.js │ ├── jsconfig.json │ ├── package.json │ ├── src │ │ ├── directives │ │ │ ├── requireAuth │ │ │ │ ├── requireAuth.test.ts │ │ │ │ └── requireAuth.ts │ │ │ └── skipAuth │ │ │ │ ├── skipAuth.test.ts │ │ │ │ └── skipAuth.ts │ │ ├── docker │ │ │ ├── .env.example │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── aws-emulator.js │ │ │ ├── cadquery │ │ │ │ ├── Dockerfile │ │ │ │ ├── cadquery.ts │ │ │ │ └── runCQ.ts │ │ │ ├── common │ │ │ │ ├── entrypoint.sh │ │ │ │ ├── node14source_setup.sh │ │ │ │ └── utils.ts │ │ │ ├── curv │ │ │ │ ├── Dockerfile │ │ │ │ ├── curv.ts │ │ │ │ └── runCurv.ts │ │ │ ├── docker-compose.yml │ │ │ ├── openscad │ │ │ │ ├── Dockerfile │ │ │ │ ├── cadhubtheme.json │ │ │ │ ├── openscad.ts │ │ │ │ └── runScad.ts │ │ │ ├── serverless.yml │ │ │ └── yarn.lock │ │ ├── functions │ │ │ ├── check-user-name │ │ │ │ └── check-user-name.ts │ │ │ ├── graphql.ts │ │ │ └── identity-signup.ts │ │ ├── graphql │ │ │ ├── .keep │ │ │ ├── ProjectReactions.sdl.js │ │ │ ├── comments.sdl.js │ │ │ ├── email.sdl.ts │ │ │ ├── projects.sdl.ts │ │ │ ├── socialCards.sdl.ts │ │ │ ├── subjectAccessRequests.sdl.js │ │ │ └── users.sdl.js │ │ ├── lib │ │ │ ├── auth.ts │ │ │ ├── cloudinary.ts │ │ │ ├── db.js │ │ │ ├── discord.ts │ │ │ ├── logger.ts │ │ │ ├── owner.ts │ │ │ ├── sendmail.ts │ │ │ └── sentry.ts │ │ └── services │ │ │ ├── .keep │ │ │ ├── comments │ │ │ └── comments.ts │ │ │ ├── email │ │ │ └── email.ts │ │ │ ├── helpers.ts │ │ │ ├── projectReactions │ │ │ └── projectReactions.js │ │ │ ├── projects │ │ │ └── projects.ts │ │ │ ├── socialCards │ │ │ └── socialCards.ts │ │ │ ├── subjectAccessRequests │ │ │ └── subjectAccessRequests.js │ │ │ └── users │ │ │ └── users.ts │ └── tsconfig.json ├── babel.config.js ├── graphql.config.js ├── netlify.toml ├── package.json ├── prettier.config.js ├── redwood.toml ├── scripts │ └── seed.ts ├── web │ ├── .babelrc.js │ ├── config │ │ ├── postcss.config.js │ │ ├── tailwind.config.js │ │ ├── webpack.config.js │ │ └── worker-loader.d.ts │ ├── identity-test.json │ ├── jest.config.js │ ├── jsconfig.json │ ├── package.json │ ├── public │ │ ├── README.md │ │ ├── default-social-image.jpg │ │ ├── hinge.stl │ │ ├── pumpjack.stl │ │ └── robots.txt │ ├── src │ │ ├── App.tsx │ │ ├── Routes.js │ │ ├── components │ │ │ ├── .keep │ │ │ ├── AdminProjects │ │ │ │ └── AdminProjects.tsx │ │ │ ├── AdminProjectsCell │ │ │ │ └── AdminProjectsCell.tsx │ │ │ ├── Button │ │ │ │ ├── Button.js │ │ │ │ ├── Button.stories.js │ │ │ │ └── Button.test.js │ │ │ ├── CadPackage │ │ │ │ └── CadPackage.tsx │ │ │ ├── CaptureButton │ │ │ │ └── CaptureButton.tsx │ │ │ ├── ConfirmDialog │ │ │ │ ├── ConfirmDialog.js │ │ │ │ ├── ConfirmDialog.stories.js │ │ │ │ └── ConfirmDialog.test.js │ │ │ ├── Customizer │ │ │ │ ├── Customizer.tsx │ │ │ │ └── customizerConverter.ts │ │ │ ├── DelayedPingAnimation │ │ │ │ └── DelayedPingAnimation.tsx │ │ │ ├── EditSubjectAccessRequestCell │ │ │ │ └── EditSubjectAccessRequestCell.js │ │ │ ├── EditUserCell │ │ │ │ └── EditUserCell.tsx │ │ │ ├── EditableProjecTitle │ │ │ │ └── EditableProjecTitle.tsx │ │ │ ├── EditorGuide │ │ │ │ └── EditorGuide.tsx │ │ │ ├── EditorMenu │ │ │ │ ├── AllShortcutsModal.tsx │ │ │ │ ├── Dropdowns.tsx │ │ │ │ ├── EditorMenu.tsx │ │ │ │ └── menuConfig.tsx │ │ │ ├── EmbedProject │ │ │ │ └── EmbedProject.tsx │ │ │ ├── EmbedProjectCell │ │ │ │ ├── EmbedProjectCell.mock.ts │ │ │ │ ├── EmbedProjectCell.stories.tsx │ │ │ │ ├── EmbedProjectCell.test.tsx │ │ │ │ └── EmbedProjectCell.tsx │ │ │ ├── EmbedViewer │ │ │ │ └── EmbedViewer.tsx │ │ │ ├── EmojiReaction │ │ │ │ └── EmojiReaction.tsx │ │ │ ├── EncodedUrl │ │ │ │ ├── ExternalScript.tsx │ │ │ │ ├── FullScriptEncoding.tsx │ │ │ │ └── helpers.ts │ │ │ ├── FatalErrorBoundary │ │ │ │ └── FatalErrorBoundary.tsx │ │ │ ├── Footer │ │ │ │ └── Footer.tsx │ │ │ ├── Gravatar │ │ │ │ └── Gravatar.tsx │ │ │ ├── Hero │ │ │ │ ├── AssetWithGooey.tsx │ │ │ │ └── Hero.tsx │ │ │ ├── IdeConsole │ │ │ │ └── IdeConsole.tsx │ │ │ ├── IdeContainer │ │ │ │ └── IdeContainer.tsx │ │ │ ├── IdeEditor │ │ │ │ └── IdeEditor.tsx │ │ │ ├── IdeHeader │ │ │ │ └── IdeHeader.tsx │ │ │ ├── IdeProjectCell │ │ │ │ ├── IdeProjectCell.mock.ts │ │ │ │ ├── IdeProjectCell.stories.tsx │ │ │ │ ├── IdeProjectCell.test.tsx │ │ │ │ └── IdeProjectCell.tsx │ │ │ ├── IdeSideBar │ │ │ │ ├── IdeSideBar.tsx │ │ │ │ └── sidebarConfig.tsx │ │ │ ├── IdeViewer │ │ │ │ ├── Asset.tsx │ │ │ │ ├── IdeViewer.tsx │ │ │ │ ├── PureIdeViewer.tsx │ │ │ │ └── dullFrontLitMetal.png │ │ │ ├── IdeWrapper │ │ │ │ ├── IdeWrapper.test.js │ │ │ │ ├── IdeWrapper.tsx │ │ │ │ ├── useRender.ts │ │ │ │ └── useSaveCode.ts │ │ │ ├── ImageUploader │ │ │ │ ├── ImageUploader.js │ │ │ │ ├── ImageUploader.stories.js │ │ │ │ └── ImageUploader.test.js │ │ │ ├── InputText │ │ │ │ ├── InputText.js │ │ │ │ ├── InputText.stories.js │ │ │ │ └── InputText.test.js │ │ │ ├── InputTextForm │ │ │ │ ├── InputTextForm.stories.js │ │ │ │ ├── InputTextForm.test.js │ │ │ │ └── InputTextForm.tsx │ │ │ ├── KeyValue │ │ │ │ └── KeyValue.tsx │ │ │ ├── LandingSection │ │ │ │ ├── LandingSection.js │ │ │ │ ├── LandingSection.stories.js │ │ │ │ ├── LandingSection.test.js │ │ │ │ └── mockEditorParts.js │ │ │ ├── LoginModal │ │ │ │ ├── LoginModal.stories.js │ │ │ │ ├── LoginModal.test.js │ │ │ │ └── LoginModal.tsx │ │ │ ├── LogoType │ │ │ │ └── LogoType.js │ │ │ ├── NavPlusButton │ │ │ │ └── NavPlusButton.tsx │ │ │ ├── OutBound │ │ │ │ ├── OutBound.js │ │ │ │ ├── OutBound.stories.js │ │ │ │ └── OutBound.test.js │ │ │ ├── PanelToolbar │ │ │ │ └── PanelToolbar.tsx │ │ │ ├── ProfileSlashLogin │ │ │ │ ├── ProfileSlashLogin.stories.tsx │ │ │ │ ├── ProfileSlashLogin.test.tsx │ │ │ │ └── ProfileSlashLogin.tsx │ │ │ ├── ProfileTextInput │ │ │ │ ├── ProfileTextInput.js │ │ │ │ ├── ProfileTextInput.stories.js │ │ │ │ └── ProfileTextInput.test.js │ │ │ ├── ProfileViewer │ │ │ │ └── ProfileViewer.tsx │ │ │ ├── ProjectCard │ │ │ │ └── ProjectCard.tsx │ │ │ ├── ProjectCell │ │ │ │ ├── ProjectCell.mock.ts │ │ │ │ ├── ProjectCell.stories.tsx │ │ │ │ ├── ProjectCell.test.tsx │ │ │ │ └── ProjectCell.tsx │ │ │ ├── ProjectForm │ │ │ │ └── ProjectForm.tsx │ │ │ ├── ProjectProfile │ │ │ │ └── ProjectProfile.tsx │ │ │ ├── ProjectReactions │ │ │ │ └── ProjectReactions.tsx │ │ │ ├── ProjectReactionsCell │ │ │ │ ├── ProjectReactionsCell.mock.ts │ │ │ │ ├── ProjectReactionsCell.stories.tsx │ │ │ │ ├── ProjectReactionsCell.test.tsx │ │ │ │ └── ProjectReactionsCell.tsx │ │ │ ├── Projects │ │ │ │ └── Projects.tsx │ │ │ ├── ProjectsCell │ │ │ │ └── ProjectsCell.tsx │ │ │ ├── ProjectsOfUserCell │ │ │ │ ├── ProjectsOfUserCell.mock.ts │ │ │ │ ├── ProjectsOfUserCell.stories.jsx │ │ │ │ ├── ProjectsOfUserCell.test.tsx │ │ │ │ └── ProjectsOfUserCell.tsx │ │ │ ├── RecentProjectsCell │ │ │ │ └── RecentProjectsCell.tsx │ │ │ ├── Seo │ │ │ │ └── Seo.tsx │ │ │ ├── SocialCardCell │ │ │ │ └── SocialCardCell.tsx │ │ │ ├── StaticImageMessage │ │ │ │ └── StaticImageMessage.tsx │ │ │ ├── SubjectAccessRequest │ │ │ │ └── SubjectAccessRequest.js │ │ │ ├── SubjectAccessRequestCell │ │ │ │ └── SubjectAccessRequestCell.js │ │ │ ├── SubjectAccessRequestForm │ │ │ │ └── SubjectAccessRequestForm.js │ │ │ ├── SubjectAccessRequests │ │ │ │ └── SubjectAccessRequests.js │ │ │ ├── SubjectAccessRequestsCell │ │ │ │ └── SubjectAccessRequestsCell.js │ │ │ ├── Svg │ │ │ │ └── Svg.tsx │ │ │ ├── Toggle │ │ │ │ └── Toggle.tsx │ │ │ ├── TopNav │ │ │ │ └── TopNav.tsx │ │ │ ├── UserProfile │ │ │ │ ├── UserProfile.stories.js │ │ │ │ ├── UserProfile.test.js │ │ │ │ ├── UserProfile.tsx │ │ │ │ └── userProfileConfig.tsx │ │ │ ├── Users │ │ │ │ └── Users.js │ │ │ └── UsersCell │ │ │ │ └── UsersCell.js │ │ ├── favicon.svg │ │ ├── font-imports.css │ │ ├── globals.d.ts │ │ ├── helpers │ │ │ ├── cadPackages │ │ │ │ ├── cadQuery │ │ │ │ │ ├── cadQueryController.ts │ │ │ │ │ ├── cadQueryParams.ts │ │ │ │ │ ├── initialCode.py │ │ │ │ │ └── userGuide.md │ │ │ │ ├── common.ts │ │ │ │ ├── curv │ │ │ │ │ ├── curvController.ts │ │ │ │ │ ├── initialCode.curv │ │ │ │ │ └── userGuide.md │ │ │ │ ├── demoController.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jsCad │ │ │ │ │ ├── initialCode.jscad.js │ │ │ │ │ ├── jsCadController.tsx │ │ │ │ │ ├── jscadParams.ts │ │ │ │ │ ├── jscadWorker.ts │ │ │ │ │ └── userGuide.md │ │ │ │ └── openScad │ │ │ │ │ ├── initialCode.scad │ │ │ │ │ ├── openScadController.ts │ │ │ │ │ ├── openScadParams.ts │ │ │ │ │ └── userGuide.md │ │ │ ├── canvasToBlob.ts │ │ │ ├── clipboard.tsx │ │ │ ├── compress.ts │ │ │ ├── download_stl.ts │ │ │ ├── emote.js │ │ │ ├── hooks │ │ │ │ ├── use3dViewerResize.ts │ │ │ │ ├── useEdgeSplit.ts │ │ │ │ ├── useIdeContext.ts │ │ │ │ ├── useIdeState.ts │ │ │ │ ├── useKeyPress.js │ │ │ │ ├── useMarkdownMetaData.ts │ │ │ │ ├── useUpdateProject.ts │ │ │ │ ├── useUpdateProjectImages.ts │ │ │ │ └── useUser.ts │ │ │ └── subscribe.ts │ │ ├── index.css │ │ ├── index.html │ │ ├── layouts │ │ │ ├── .keep │ │ │ ├── MainLayout │ │ │ │ ├── MainLayout.css │ │ │ │ ├── MainLayout.js │ │ │ │ ├── MainLayout.stories.js │ │ │ │ └── MainLayout.test.js │ │ │ └── SubjectAccessRequestsLayout │ │ │ │ └── SubjectAccessRequestsLayout.js │ │ ├── pages │ │ │ ├── AccountRecoveryPage │ │ │ │ ├── AccountRecoveryPage.js │ │ │ │ ├── AccountRecoveryPage.stories.js │ │ │ │ └── AccountRecoveryPage.test.js │ │ │ ├── AdminEmailPage │ │ │ │ └── AdminEmailPage.tsx │ │ │ ├── AdminProjectsPage │ │ │ │ └── AdminProjectsPage.tsx │ │ │ ├── CodeOfConductPage │ │ │ │ ├── CodeOfConductPage.js │ │ │ │ ├── CodeOfConductPage.stories.js │ │ │ │ └── CodeOfConductPage.test.js │ │ │ ├── DevIdePage │ │ │ │ └── DevIdePage.tsx │ │ │ ├── DraftProjectPage │ │ │ │ ├── DraftProjectPage.stories.tsx │ │ │ │ ├── DraftProjectPage.test.tsx │ │ │ │ └── DraftProjectPage.tsx │ │ │ ├── EditProjectPage │ │ │ │ ├── EditProjectPage.stories.tsx │ │ │ │ ├── EditProjectPage.test.tsx │ │ │ │ └── EditProjectPage.tsx │ │ │ ├── EditSubjectAccessRequestPage │ │ │ │ └── EditSubjectAccessRequestPage.js │ │ │ ├── EditUserPage │ │ │ │ └── EditUserPage.js │ │ │ ├── EmbedProjectPage │ │ │ │ ├── EmbedProjectPage.test.tsx │ │ │ │ └── EmbedProjectPage.tsx │ │ │ ├── FatalErrorPage │ │ │ │ └── FatalErrorPage.js │ │ │ ├── HomePage │ │ │ │ └── HomePage.tsx │ │ │ ├── IdeProjectPage │ │ │ │ ├── IdeProjectPage.test.tsx │ │ │ │ └── IdeProjectPage.tsx │ │ │ ├── NewProjectPage │ │ │ │ ├── NewProjectPage.stories.tsx │ │ │ │ ├── NewProjectPage.test.tsx │ │ │ │ └── NewProjectPage.tsx │ │ │ ├── NotFoundPage │ │ │ │ └── NotFoundPage.js │ │ │ ├── PrivacyPolicyPage │ │ │ │ ├── PrivacyPolicyPage.js │ │ │ │ ├── PrivacyPolicyPage.stories.js │ │ │ │ └── PrivacyPolicyPage.test.js │ │ │ ├── ProjectPage │ │ │ │ ├── ProjectPage.stories.tsx │ │ │ │ ├── ProjectPage.test.tsx │ │ │ │ └── ProjectPage.tsx │ │ │ ├── ProjectsPage │ │ │ │ └── ProjectsPage.tsx │ │ │ ├── SocialCardPage │ │ │ │ └── SocialCardPage.tsx │ │ │ ├── SubjectAccessRequestPage │ │ │ │ ├── SubjectAccessRequestPage.js │ │ │ │ ├── SubjectAccessRequestPage.stories.js │ │ │ │ └── SubjectAccessRequestPage.test.js │ │ │ ├── SubjectAccessRequestsPage │ │ │ │ └── SubjectAccessRequestsPage.js │ │ │ ├── UpdatePasswordPage │ │ │ │ ├── UpdatePasswordPage.js │ │ │ │ ├── UpdatePasswordPage.stories.js │ │ │ │ └── UpdatePasswordPage.test.js │ │ │ ├── UserPage │ │ │ │ └── UserPage.js │ │ │ └── UsersPage │ │ │ │ └── UsersPage.js │ │ └── scaffold.css │ └── tsconfig.json └── yarn.lock └── docs ├── .gitignore ├── .nvmrc ├── README.md ├── babel.config.js ├── blog ├── 2020-09-06-openscad-review.md ├── 2020-10-31-curated-code-cad.md ├── 2021-03-16-3d-diffs.mdx ├── 2021-03-30-code-cad-testing.md ├── 2021-05-22-why-I-started-codhub.md ├── 2021-05-29-artifacts.md ├── 2021-06-17-right-level-of-abstraction.mdx ├── 2021-07-04-ux-case-studies-intro.mdx ├── 2021-08-14-ux-studies-timeline.mdx └── 2021-10-22-expressing-intent-in-parametric-cad.mdx ├── docs ├── definitive-beginners │ ├── adding-clearances.mdx │ ├── adding-fillets.mdx │ ├── extruding-2d-shapes.mdx │ ├── loops.mdx │ ├── modifiers.mdx │ ├── module-arguments.mdx │ ├── modules.mdx │ ├── the-basics.mdx │ ├── wrap-up.mdx │ └── your-openscad-journey.mdx ├── general-cadhub │ ├── external-resource-url.mdx │ ├── integrations.mdx │ ├── openscad-fonts.mdx │ └── openscad-previews.mdx ├── getting-started │ └── getting-started.mdx ├── round-anything │ ├── api-reference.mdx │ ├── overview.mdx │ └── radii-conflict.mdx └── why-code-cad.mdx ├── docusaurus.config.js ├── netlify.toml ├── package.json ├── plugins └── docusaurus-tailwindcss-loader │ └── index.js ├── sidebars.js ├── src ├── css │ └── custom.css └── pages │ ├── index.js │ ├── markdown-page.md │ └── styles.module.css ├── static ├── .nojekyll └── img │ ├── blog │ ├── 3d-diff │ │ ├── 3dDiffExample.jpg │ │ └── 3dDiffExampleCode.jpg │ ├── abstraction-level │ │ ├── bad-selection.jpg │ │ ├── combine-extrusions.jpg │ │ ├── complexSketchExample.png │ │ ├── csgTransfer1.jpg │ │ ├── csgTransfer2.jpg │ │ ├── duplication.png │ │ ├── extrude-cube.jpg │ │ ├── extrudeToFace3.jpg │ │ ├── extrudeToFace6.jpg │ │ ├── face-intersection.jpg │ │ ├── handles-from-tag.jpg │ │ ├── select-by-axis.jpg │ │ ├── sketch.jpg │ │ ├── surgical-fillet.jpg │ │ ├── tangentalArcSketch.png │ │ └── union-radius.jpg │ ├── curated-code-cad │ │ └── CadHubSS.jpg │ ├── intent │ │ └── heuristics.jpg │ └── ux-case-studies-intro │ │ └── ivan-sutherland-sketchpad.jpg │ ├── favicon.svg │ ├── general-cadhub │ ├── external-script-1.png │ ├── external-script-2.png │ └── space-donut.png │ ├── getting-started │ ├── complete-hinge.png │ ├── cube.jpg │ ├── ide.png │ ├── openscad-select.jpg │ └── plus.jpg │ ├── logo.svg │ ├── openscad-tutorial │ ├── big-radius.png │ ├── both-halves.png │ ├── circle-cube.png │ ├── clearance-pivot.png │ ├── difference-markup.png │ ├── difference.png │ ├── exaggerated-clearance.png │ ├── extrude.png │ ├── extrude2.png │ ├── finished-hinge.png │ ├── hole1.png │ ├── hole2.png │ ├── hole3.png │ ├── hole4.png │ ├── hole5.png │ ├── hole6.png │ ├── multi-rotate.png │ ├── no-fillet.png │ ├── offset1.png │ ├── offset2.png │ ├── offset3.png │ ├── parametric.png │ ├── pivot-gap.png │ ├── pivot.png │ ├── plain-cube.png │ ├── profile.png │ ├── read-scad.png │ ├── remove-artifact.png │ ├── rotate.png │ ├── tall-cube.png │ ├── translate.png │ ├── transparent-assembly.png │ ├── transparent-rotate.png │ └── unrounded-profile.png │ └── round-anything │ ├── api │ ├── beamchain-api-2.png │ ├── beamchain-api-3.png │ ├── beamchain-api.png │ ├── beamchain-flare.png │ ├── conflict-api.png │ ├── conflict-deep.png │ ├── extrude-with-radius-api.png │ ├── minkowski-demo.png │ ├── mirrorpoints-api.png │ ├── polyround-api.png │ ├── polyround-extrude-api.png │ ├── shell-2d-api.png │ ├── shell-2d-flare.png │ ├── translate-points-api.png │ └── translate-points-flair.png │ ├── beam-chain.png │ ├── conflict-resolution.png │ ├── minkowski-rounded.png │ ├── mirror-points.png │ ├── point-translation.png │ ├── polyround-demo.png │ ├── polyround-extrude.png │ ├── radius-extrude.png │ ├── shell-2d.png │ └── social-media-snippets.png ├── tailwind.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .history 3 | .DS_Store 4 | .env 5 | .netlify 6 | .redwood 7 | dev.db 8 | dist 9 | dist-babel 10 | node_modules 11 | yarn-error.log 12 | 13 | # serverless related ignores, see api/src/docker/openscad/Dockerfile for aws-lambda-rie info 14 | .serverless 15 | aws-lambda-rie 16 | 17 | # docs 18 | docs/.docusaurus 19 | docs/build 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/.gitmodules -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Cadhub", 4 | "cadquery", 5 | "curv", 6 | "Customizer", 7 | "Hutten", 8 | "jscad", 9 | "openscad", 10 | "sendmail" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Screen Recording 2021-09-21 at 8](https://user-images.githubusercontent.com/29681384/134154332-65491787-7b36-4ad9-ba7a-bac0f2874051.gif) 2 | ![scrch2](https://user-images.githubusercontent.com/29681384/134156021-6b55c301-a77a-4851-b67b-b656875123e5.jpg) 3 | 4 | 5 | # [C a d H u b](https://cadhub.xyz) 6 | 7 | 8 | 9 | Let's help Code-CAD reach its [full potential!](https://cadhub.xyz) We're making a ~~cad~~hub for the Code-CAD community, think of it as model-repository crossed with a live editor. We have integrations in progress for [OpenSCAD](https://cadhub.xyz/dev-ide/openscad) and [CadQuery](https://cadhub.xyz/dev-ide/cadquery) with [more coming soon](https://github.com/Irev-Dev/curated-code-cad). 10 | 11 | If you want to be involved in anyway, checkout the [contributing.md](https://github.com/Irev-Dev/cadhub/blob/main/CONTRIBUTING.md). 12 | 13 | you might also be interested in the [Road Map](https://github.com/Irev-Dev/cadhub/discussions/212) and getting in touch via, [twitter](https://twitter.com/IrevDev), [discord](https://discord.gg/SD7zFRNjGH) or [discussions](https://github.com/Irev-Dev/cadhub/discussions). 14 | 15 | ## Who is CadHub 16 | 17 | [Kurt](https://github.com/Irev-Dev) and [Frank](https://github.com/franknoirot) make up the Core-team and [Jeremy](https://github.com/jmwright), [Torsten](https://github.com/t-paul) and [Hrg](https://github.com/hrgdavor) are a major contributors. Plus a number smaller contributors. 18 | -------------------------------------------------------------------------------- /app/.env.defaults: -------------------------------------------------------------------------------- 1 | # These environment variables will be used by default if you do not create any 2 | # yourself in .env. This file should be safe to check into your version control 3 | # system. Any custom values should go in .env and .env should *not* be checked 4 | # into version control. 5 | 6 | # schema.prisma defaults 7 | DATABASE_URL=file:./dev.db 8 | 9 | # disables Prisma CLI update notifier 10 | PRISMA_HIDE_UPDATE_MESSAGE=true 11 | 12 | CLOUDINARY_API_KEY=476712943135152 13 | # ask Kurt for help getting set up with a secret 14 | # CLOUDINARY_API_SECRET= 15 | 16 | # Option to override the current environment's default api-side log level 17 | # See: https://redwoodjs.com/docs/logger for level options: 18 | # trace | info | debug | warn | error | silent 19 | # LOG_LEVEL=debug 20 | 21 | # EMAIL_PASSWORD=abc123 22 | # DISCORD_TOKEN=abc123 23 | # DISCORD_CHANNEL_ID=12345 24 | 25 | # CAD_LAMBDA_BASE_URL="http://localhost:8080" 26 | 27 | # sentry 28 | GITHUB_ASSIST_APP_ID=23342 29 | GITHUB_ASSIST_SECRET=abc 30 | GITHUB_ASSIST_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nabcdefg\n-----END RSA PRIVATE KEY-----" 31 | -------------------------------------------------------------------------------- /app/.env.example: -------------------------------------------------------------------------------- 1 | # DATABASE_URL=file:./dev.db 2 | # BINARY_TARGET=native -------------------------------------------------------------------------------- /app/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | web/types/graphql.d.ts 3 | api/types/graphql.d.ts 4 | -------------------------------------------------------------------------------- /app/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "eamodio.gitlens", 5 | "ofhumanbondage.react-proptypes-intellisense", 6 | "mgmcdermott.vscode-language-babel", 7 | "wix.vscode-import-cost", 8 | "pflannery.vscode-versionlens", 9 | "editorconfig.editorconfig", 10 | "prisma.prisma" 11 | ], 12 | "unwantedRecommendations": [] 13 | } 14 | -------------------------------------------------------------------------------- /app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "yarn redwood dev", 6 | "name": "launch development", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | }, 10 | { 11 | "type": "pwa-node", 12 | "request": "launch", 13 | "name": "launch api", 14 | "skipFiles": [ 15 | "/**" 16 | ], 17 | "cwd": "${workspaceFolder}/api", 18 | "envFile": "${workspaceFolder}/.env.defaults", 19 | "program": "${workspaceFolder}/node_modules/.bin/dev-server" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "files.trimTrailingWhitespace": true, 4 | "editor.formatOnSave": false, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": true 7 | }, 8 | "[prisma]": { 9 | "editor.formatOnSave": true 10 | }, 11 | "eslint.workingDirectories": [ 12 | "./api", 13 | "./web/src/components", 14 | "./web/src/layouts", 15 | "./web/src/pages", 16 | "./web/src/index.js", 17 | "./web/src/Routes.js", 18 | ], 19 | "cSpell.words": [ 20 | "Cadhub", 21 | "Initialised", 22 | "Uploader", 23 | "describedby", 24 | "initialise", 25 | "redwoodjs", 26 | "resizer", 27 | "roboto", 28 | "ropa" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /app/api/db/dataMigrations/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/api/db/dataMigrations/.keep -------------------------------------------------------------------------------- /app/api/db/migrations/20210704054715_create_data_migrations/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "RW_DataMigration" ( 3 | "version" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "startedAt" TIMESTAMP(3) NOT NULL, 6 | "finishedAt" TIMESTAMP(3) NOT NULL, 7 | 8 | PRIMARY KEY ("version") 9 | ); 10 | -------------------------------------------------------------------------------- /app/api/db/migrations/20210708100818_drop_cascade_tables/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `Comment` table. If the table is not empty, all the data it contains will be lost. 5 | - You are about to drop the `Part` table. If the table is not empty, all the data it contains will be lost. 6 | - You are about to drop the `PartReaction` table. If the table is not empty, all the data it contains will be lost. 7 | 8 | */ 9 | -- DropForeignKey 10 | ALTER TABLE "Comment" DROP CONSTRAINT "Comment_partId_fkey"; 11 | 12 | -- DropForeignKey 13 | ALTER TABLE "Comment" DROP CONSTRAINT "Comment_userId_fkey"; 14 | 15 | -- DropForeignKey 16 | ALTER TABLE "Part" DROP CONSTRAINT "Part_userId_fkey"; 17 | 18 | -- DropForeignKey 19 | ALTER TABLE "PartReaction" DROP CONSTRAINT "PartReaction_partId_fkey"; 20 | 21 | -- DropForeignKey 22 | ALTER TABLE "PartReaction" DROP CONSTRAINT "PartReaction_userId_fkey"; 23 | 24 | -- DropTable 25 | DROP TABLE "Comment"; 26 | 27 | -- DropTable 28 | DROP TABLE "Part"; 29 | 30 | -- DropTable 31 | DROP TABLE "PartReaction"; 32 | -------------------------------------------------------------------------------- /app/api/db/migrations/20210716102203_add_cad_package_to_projects/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "CadPackage" AS ENUM ('openscad', 'cadquery'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Project" ADD COLUMN "cadPackage" "CadPackage" NOT NULL DEFAULT E'openscad'; 6 | -------------------------------------------------------------------------------- /app/api/db/migrations/20210812210054_add_social_image/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "SocialCard" ( 3 | "id" TEXT NOT NULL, 4 | "projectId" TEXT NOT NULL, 5 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "updatedAt" TIMESTAMP(3) NOT NULL, 7 | "url" TEXT, 8 | "outOfDate" BOOLEAN NOT NULL DEFAULT true, 9 | 10 | PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "SocialCard_projectId_unique" ON "SocialCard"("projectId"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "SocialCard" ADD FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /app/api/db/migrations/20210815062510_make_social_card_one_to_one_with_project/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterIndex 2 | ALTER INDEX "SocialCard_projectId_unique" RENAME TO "SocialCard.projectId_unique"; 3 | -------------------------------------------------------------------------------- /app/api/db/migrations/20210912041733_add_jscad/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "CadPackage" ADD VALUE 'jscad'; 3 | -------------------------------------------------------------------------------- /app/api/db/migrations/20210925001652_add_project_forking/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Project" ADD COLUMN "forkedFromId" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "Project" ADD FOREIGN KEY ("forkedFromId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /app/api/db/migrations/20211129205924_curv/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "CadPackage" ADD VALUE 'curv'; 3 | -------------------------------------------------------------------------------- /app/api/db/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /app/api/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@redwoodjs/testing/config/jest/api') 2 | -------------------------------------------------------------------------------- /app/api/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": ["./src/*"] 6 | } 7 | }, 8 | "include": ["src/**/*", "../.redwood/index.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /app/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@redwoodjs/api": "^0.38.1", 7 | "@redwoodjs/graphql-server": "^0.38.1", 8 | "@sentry/node": "^6.5.1", 9 | "axios": "^0.25.0", 10 | "cloudinary": "^1.23.0", 11 | "cors": "^2.8.5", 12 | "express": "^4.17.1", 13 | "human-id": "^2.0.1", 14 | "middy": "^0.36.0", 15 | "nanoid": "^3.1.20", 16 | "nodemailer": "^6.6.2", 17 | "serverless-binary-cors": "^0.0.1" 18 | }, 19 | "devDependencies": { 20 | "@types/nodemailer": "^6.4.2", 21 | "concurrently": "^6.0.0", 22 | "nodemon": "^2.0.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/api/src/directives/requireAuth/requireAuth.test.ts: -------------------------------------------------------------------------------- 1 | import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api' 2 | 3 | import requireAuth from './requireAuth' 4 | 5 | describe('requireAuth directive', () => { 6 | it('declares the directive sdl as schema, with the correct name', () => { 7 | expect(requireAuth.schema).toBeTruthy() 8 | expect(getDirectiveName(requireAuth.schema)).toBe('requireAuth') 9 | }) 10 | 11 | it('requireAuth has stub implementation. Should not throw when current user', () => { 12 | // If you want to set values in context, pass it through e.g. 13 | // mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }}) 14 | const mockExecution = mockRedwoodDirective(requireAuth, { context: {} }) 15 | 16 | expect(mockExecution).not.toThrowError() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /app/api/src/directives/requireAuth/requireAuth.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | import { createValidatorDirective } from '@redwoodjs/graphql-server' 4 | 5 | import { requireAuth as applicationRequireAuth } from 'src/lib/auth' 6 | 7 | export const schema = gql` 8 | """ 9 | Use to check whether or not a user is authenticated and is associated 10 | with an optional set of roles. 11 | """ 12 | directive @requireAuth(roles: [String]) on FIELD_DEFINITION 13 | ` 14 | 15 | const validate = ({ directiveArgs }) => { 16 | const { roles } = directiveArgs 17 | applicationRequireAuth({ roles }) 18 | } 19 | 20 | const requireAuth = createValidatorDirective(schema, validate) 21 | 22 | export default requireAuth 23 | -------------------------------------------------------------------------------- /app/api/src/directives/skipAuth/skipAuth.test.ts: -------------------------------------------------------------------------------- 1 | import { getDirectiveName } from '@redwoodjs/testing/api' 2 | 3 | import skipAuth from './skipAuth' 4 | 5 | describe('skipAuth directive', () => { 6 | it('declares the directive sdl as schema, with the correct name', () => { 7 | expect(skipAuth.schema).toBeTruthy() 8 | expect(getDirectiveName(skipAuth.schema)).toBe('skipAuth') 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /app/api/src/directives/skipAuth/skipAuth.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | import { createValidatorDirective } from '@redwoodjs/graphql-server' 4 | 5 | export const schema = gql` 6 | """ 7 | Use to skip authentication checks and allow public access. 8 | """ 9 | directive @skipAuth on FIELD_DEFINITION 10 | ` 11 | 12 | const skipAuth = createValidatorDirective(schema, () => { 13 | return 14 | }) 15 | 16 | export default skipAuth 17 | -------------------------------------------------------------------------------- /app/api/src/docker/.env.example: -------------------------------------------------------------------------------- 1 | # The following are the env vars you need run the cad lamdas locally 2 | # The still connect to s3 so some secrets are needed, ask Kurt and he'll set things up for you 3 | DEV_AWS_SECRET_ACCESS_KEY="" 4 | DEV_AWS_ACCESS_KEY_ID="" 5 | DEV_BUCKET="cad-preview-bucket-dev-001" 6 | -------------------------------------------------------------------------------- /app/api/src/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | COPY package.json ./ 4 | COPY yarn.lock ./ 5 | COPY aws-emulator.js ./ 6 | 7 | RUN npm install 8 | 9 | EXPOSE 8080 10 | 11 | CMD ["node", "./aws-emulator.js"] 12 | -------------------------------------------------------------------------------- /app/api/src/docker/aws-emulator.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | var cors = require('cors') 3 | const axios = require('axios') 4 | const app = express() 5 | const port = 8080 6 | app.use(express.json()) 7 | app.use(cors()) 8 | 9 | const invocationURL = (port) => 10 | `http://localhost:${port}/2015-03-31/functions/function/invocations` 11 | 12 | const makeRequest = (route, port) => [ 13 | route, 14 | async (req, res) => { 15 | console.log(`making post request to ${port}, ${route}`) 16 | try { 17 | const { data } = await axios.post(invocationURL(port), { 18 | body: Buffer.from(JSON.stringify(req.body)).toString('base64'), 19 | }) 20 | res.status(data.statusCode) 21 | res.setHeader('Content-Type', 'application/javascript') 22 | if (data.headers && data.headers['Content-Encoding'] === 'gzip') { 23 | res.setHeader('Content-Encoding', 'gzip') 24 | res.send(Buffer.from(data.body, 'base64')) 25 | } else { 26 | res.send(Buffer.from(data.body, 'base64')) 27 | } 28 | } catch (e) { 29 | res.status(500) 30 | res.send() 31 | } 32 | }, 33 | ] 34 | 35 | app.post(...makeRequest('/openscad/preview', 5052)) 36 | app.post(...makeRequest('/openscad/stl', 5053)) 37 | 38 | app.post(...makeRequest('/cadquery/stl', 5060)) 39 | 40 | app.post(...makeRequest('/curv/preview', 5070)) 41 | app.post(...makeRequest('/curv/stl', 5071)) 42 | 43 | app.listen(port, () => { 44 | console.log(`Example app listening at http://localhost:${port}`) 45 | }) 46 | -------------------------------------------------------------------------------- /app/api/src/docker/cadquery/cadquery.ts: -------------------------------------------------------------------------------- 1 | import { runCQ } from './runCQ' 2 | import middy from 'middy' 3 | import { cors } from 'middy/middlewares' 4 | import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils' 5 | 6 | const stl = async (req, _context, callback) => { 7 | _context.callbackWaitsForEmptyEventLoop = false 8 | const eventBody = Buffer.from(req.body, 'base64').toString('ascii') 9 | console.log('eventBody', eventBody) 10 | 11 | const { file, settings } = JSON.parse(eventBody) 12 | const { error, fullPath } = await runCQ({ 13 | file, 14 | settings, 15 | }) 16 | await storeAssetAndReturnUrl({ 17 | error, 18 | callback, 19 | fullPath, 20 | consoleMessage: '', 21 | tempFile : '', 22 | }) 23 | } 24 | 25 | module.exports = { 26 | stl: middy(loggerWrap(stl)).use(cors()), 27 | } 28 | -------------------------------------------------------------------------------- /app/api/src/docker/common/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then 3 | /usr/local/bin/aws-lambda-rie /usr/bin/npx aws-lambda-ric $1 4 | else 5 | /usr/bin/npx aws-lambda-ric $1 6 | fi 7 | -------------------------------------------------------------------------------- /app/api/src/docker/curv/curv.ts: -------------------------------------------------------------------------------- 1 | import { runCurv, stlExport } from './runCurv' 2 | import middy from 'middy' 3 | import { cors } from 'middy/middlewares' 4 | import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils' 5 | 6 | const preview = async (req, _context, callback) => { 7 | _context.callbackWaitsForEmptyEventLoop = false 8 | const eventBody = Buffer.from(req.body, 'base64').toString('ascii') 9 | console.log('eventBody', eventBody) 10 | 11 | const { file, settings } = JSON.parse(eventBody) 12 | const { error, consoleMessage, fullPath, tempFile } = await runCurv({ 13 | file, 14 | settings, 15 | }) 16 | await storeAssetAndReturnUrl({ 17 | error, 18 | callback, 19 | fullPath, 20 | consoleMessage, 21 | tempFile, 22 | }) 23 | } 24 | 25 | const stl = async (req, _context, callback) => { 26 | _context.callbackWaitsForEmptyEventLoop = false 27 | const eventBody = Buffer.from(req.body, 'base64').toString('ascii') 28 | 29 | console.log(eventBody, 'eventBody') 30 | 31 | const { file, settings } = JSON.parse(eventBody) 32 | const { error, consoleMessage, fullPath, tempFile } = await stlExport({ 33 | file, 34 | settings, 35 | }) 36 | await storeAssetAndReturnUrl({ 37 | error, 38 | callback, 39 | fullPath, 40 | consoleMessage, 41 | tempFile, 42 | }) 43 | } 44 | 45 | module.exports = { 46 | stl: middy(loggerWrap(stl)).use(cors()), 47 | preview: middy(loggerWrap(preview)).use(cors()), 48 | } 49 | -------------------------------------------------------------------------------- /app/api/src/docker/openscad/cadhubtheme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "CadHub", 3 | "index" : 1600, 4 | "show-in-gui" : true, 5 | 6 | "colors" : { 7 | "background" : "#1A1A1D", 8 | "axes-color" : "#c1c1c1", 9 | "opencsg-face-front" : "#eeeeee", 10 | "opencsg-face-back" : "#8732F2", 11 | "cgal-face-front" : "#eeeeee", 12 | "cgal-face-back" : "#0babc8", 13 | "cgal-face-2d" : "#9370db", 14 | "cgal-edge-front" : "#0000ff", 15 | "cgal-edge-back" : "#0000ff", 16 | "cgal-edge-2d" : "#ff00ff", 17 | "crosshair" : "#f0f0f0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/api/src/docker/openscad/openscad.ts: -------------------------------------------------------------------------------- 1 | import { runScad, stlExport } from './runScad' 2 | import middy from 'middy' 3 | import { cors } from 'middy/middlewares' 4 | import { loggerWrap, storeAssetAndReturnUrl } from '../common/utils' 5 | 6 | const preview = async (req, _context, callback) => { 7 | _context.callbackWaitsForEmptyEventLoop = false 8 | const eventBody = Buffer.from(req.body, 'base64').toString('ascii') 9 | console.log('eventBody', eventBody) 10 | 11 | const { file, settings } = JSON.parse(eventBody) 12 | const { error, consoleMessage, fullPath, tempFile } = await runScad({ 13 | file, 14 | settings, 15 | }) 16 | await storeAssetAndReturnUrl({ 17 | error, 18 | callback, 19 | fullPath, 20 | consoleMessage, 21 | tempFile, 22 | }) 23 | } 24 | 25 | const stl = async (req, _context, callback) => { 26 | _context.callbackWaitsForEmptyEventLoop = false 27 | const eventBody = Buffer.from(req.body, 'base64').toString('ascii') 28 | 29 | console.log(eventBody, 'eventBody') 30 | 31 | const { file, settings } = JSON.parse(eventBody) 32 | const { error, consoleMessage, fullPath, tempFile } = await stlExport({ 33 | file, 34 | settings, 35 | }) 36 | await storeAssetAndReturnUrl({ 37 | error, 38 | callback, 39 | fullPath, 40 | consoleMessage, 41 | tempFile, 42 | }) 43 | } 44 | 45 | module.exports = { 46 | stl: middy(loggerWrap(stl)).use(cors()), 47 | preview: middy(loggerWrap(preview)).use(cors()), 48 | } 49 | -------------------------------------------------------------------------------- /app/api/src/functions/check-user-name/check-user-name.ts: -------------------------------------------------------------------------------- 1 | import type { APIGatewayEvent /*, Context*/ } from 'aws-lambda' 2 | import { logger } from 'src/lib/logger' 3 | import { db } from 'src/lib/db' 4 | 5 | /** 6 | * The handler function is your code that processes http request events. 7 | * You can use return and throw to send a response or error, respectively. 8 | * 9 | * Important: When deployed, a custom serverless function is an open API endpoint and 10 | * is your responsibility to secure appropriately. 11 | * 12 | * @see {@link https://redwoodjs.com/docs/serverless-functions#security-considerations|Serverless Function Considerations} 13 | * in the RedwoodJS documentation for more information. 14 | * 15 | * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent 16 | * @typedef { import('aws-lambda').Context } Context 17 | * @param { APIGatewayEvent } event - an object which contains information from the invoker. 18 | * @param { Context } context - contains information about the invocation, 19 | * function, and execution environment. 20 | */ 21 | export const handler = async (event: APIGatewayEvent /*context: Context*/) => { 22 | logger.info('Invoked checkUserName function') 23 | const userName = event.queryStringParameters.username 24 | let isUserNameAvailable = false 25 | try { 26 | const user = await db.user.findUnique({ where: { userName } }) 27 | isUserNameAvailable = !user 28 | } catch (error) { 29 | isUserNameAvailable = false 30 | } 31 | return { 32 | statusCode: 200, 33 | headers: { 34 | 'Content-Type': 'application/json', 35 | }, 36 | body: JSON.stringify({ 37 | isUserNameAvailable, 38 | }), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/api/src/functions/graphql.ts: -------------------------------------------------------------------------------- 1 | import { createGraphQLHandler } from '@redwoodjs/graphql-server' 2 | import { createSentryApolloPlugin } from 'src/lib/sentry' 3 | import { logger } from 'src/lib/logger' 4 | 5 | import directives from 'src/directives/**/*.{js,ts}' 6 | import sdls from 'src/graphql/**/*.sdl.{js,ts}' 7 | import services from 'src/services/**/*.{js,ts}' 8 | 9 | import { getCurrentUser } from 'src/lib/auth' 10 | import { db } from 'src/lib/db' 11 | 12 | export const handler = createGraphQLHandler({ 13 | loggerConfig: { logger, options: {} }, 14 | getCurrentUser, 15 | directives, 16 | sdls, 17 | services, 18 | plugins: [createSentryApolloPlugin()], 19 | 20 | onException: () => { 21 | // Disconnect from your database with an unhandled exception. 22 | db.$disconnect() 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /app/api/src/graphql/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/api/src/graphql/.keep -------------------------------------------------------------------------------- /app/api/src/graphql/ProjectReactions.sdl.js: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type ProjectReaction { 3 | id: String! 4 | emote: String! 5 | user: User! 6 | userId: String! 7 | project: Project! 8 | projectId: String! 9 | createdAt: DateTime! 10 | updatedAt: DateTime! 11 | } 12 | 13 | type Query { 14 | projectReactions: [ProjectReaction!]! @skipAuth 15 | projectReaction(id: String!): ProjectReaction @skipAuth 16 | projectReactionsByProjectId(projectId: String!): [ProjectReaction!]! 17 | @skipAuth 18 | } 19 | 20 | input ToggleProjectReactionInput { 21 | emote: String! 22 | userId: String! 23 | projectId: String! 24 | } 25 | 26 | input UpdateProjectReactionInput { 27 | emote: String 28 | userId: String 29 | projectId: String 30 | } 31 | 32 | type Mutation { 33 | toggleProjectReaction(input: ToggleProjectReactionInput!): ProjectReaction! 34 | @requireAuth 35 | updateProjectReaction( 36 | id: String! 37 | input: UpdateProjectReactionInput! 38 | ): ProjectReaction! @requireAuth 39 | deleteProjectReaction(id: String!): ProjectReaction! @requireAuth 40 | } 41 | ` 42 | -------------------------------------------------------------------------------- /app/api/src/graphql/comments.sdl.js: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type Comment { 3 | id: String! 4 | text: String! 5 | user: User! 6 | userId: String! 7 | project: Project! 8 | projectId: String! 9 | createdAt: DateTime! 10 | updatedAt: DateTime! 11 | } 12 | 13 | type Query { 14 | comments: [Comment!]! @skipAuth 15 | comment(id: String!): Comment @skipAuth 16 | } 17 | 18 | input CreateCommentInput { 19 | text: String! 20 | userId: String! 21 | projectId: String! 22 | } 23 | 24 | input UpdateCommentInput { 25 | text: String 26 | userId: String 27 | projectId: String 28 | } 29 | 30 | type Mutation { 31 | createComment(input: CreateCommentInput!): Comment! @requireAuth 32 | updateComment(id: String!, input: UpdateCommentInput!): Comment! 33 | @requireAuth 34 | deleteComment(id: String!): Comment! @requireAuth 35 | } 36 | ` 37 | -------------------------------------------------------------------------------- /app/api/src/graphql/email.sdl.ts: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type Envelope { 3 | from: String 4 | to: [String!]! 5 | } 6 | 7 | type EmailResponse { 8 | accepted: [String!]! 9 | rejected: [String!]! 10 | } 11 | 12 | input Email { 13 | subject: String! 14 | body: String! 15 | } 16 | 17 | type Mutation { 18 | sendAllUsersEmail(input: Email!): EmailResponse! @requireAuth 19 | } 20 | ` 21 | -------------------------------------------------------------------------------- /app/api/src/graphql/projects.sdl.ts: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type Project { 3 | id: String! 4 | title: String! 5 | description: String 6 | code: String 7 | mainImage: String 8 | createdAt: DateTime! 9 | updatedAt: DateTime! 10 | user: User! 11 | userId: String! 12 | deleted: Boolean! 13 | cadPackage: CadPackage! 14 | socialCard: SocialCard 15 | Comment: [Comment]! 16 | Reaction(userId: String): [ProjectReaction]! 17 | forkedFromId: String 18 | forkedFrom: Project 19 | childForks: [Project]! 20 | } 21 | 22 | # should match enum in api/db/schema.prisma 23 | enum CadPackage { 24 | openscad 25 | cadquery 26 | jscad 27 | curv 28 | } 29 | 30 | type Query { 31 | projects(userName: String): [Project!]! @skipAuth 32 | project(id: String!): Project @skipAuth 33 | projectByUserAndTitle(userName: String!, projectTitle: String!): Project 34 | @skipAuth 35 | } 36 | 37 | input CreateProjectInput { 38 | title: String 39 | description: String 40 | code: String 41 | mainImage: String 42 | userId: String! 43 | cadPackage: CadPackage! 44 | } 45 | 46 | input ForkProjectInput { 47 | userId: String! 48 | forkedFromId: String 49 | code: String 50 | } 51 | 52 | input UpdateProjectInput { 53 | title: String 54 | description: String 55 | code: String 56 | mainImage: String 57 | userId: String 58 | } 59 | 60 | type Mutation { 61 | createProject(input: CreateProjectInput!): Project! @requireAuth 62 | forkProject(input: ForkProjectInput!): Project! @requireAuth 63 | updateProject(id: String!, input: UpdateProjectInput!): Project! 64 | @requireAuth 65 | updateProjectImages( 66 | id: String! 67 | mainImage64: String 68 | socialCard64: String 69 | ): Project! @requireAuth 70 | deleteProject(id: String!): Project! @requireAuth 71 | } 72 | ` 73 | -------------------------------------------------------------------------------- /app/api/src/graphql/socialCards.sdl.ts: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type SocialCard { 3 | id: String! 4 | projectId: String! 5 | project: Project! 6 | createdAt: DateTime! 7 | updatedAt: DateTime! 8 | url: String 9 | outOfDate: Boolean! 10 | } 11 | 12 | type Query { 13 | socialCards: [SocialCard!]! @skipAuth 14 | socialCard(id: String!): SocialCard @skipAuth 15 | } 16 | ` 17 | -------------------------------------------------------------------------------- /app/api/src/graphql/subjectAccessRequests.sdl.js: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type SubjectAccessRequest { 3 | id: String! 4 | comment: String! 5 | payload: String! 6 | user: User! 7 | userId: String! 8 | createdAt: DateTime! 9 | updatedAt: DateTime! 10 | } 11 | 12 | type Query { 13 | subjectAccessRequests: [SubjectAccessRequest!]! @requireAuth 14 | subjectAccessRequest(id: String!): SubjectAccessRequest @requireAuth 15 | } 16 | 17 | input CreateSubjectAccessRequestInput { 18 | comment: String! 19 | payload: String! 20 | userId: String! 21 | } 22 | 23 | input UpdateSubjectAccessRequestInput { 24 | comment: String 25 | payload: String 26 | userId: String 27 | } 28 | 29 | type Mutation { 30 | createSubjectAccessRequest( 31 | input: CreateSubjectAccessRequestInput! 32 | ): SubjectAccessRequest! @requireAuth 33 | updateSubjectAccessRequest( 34 | id: String! 35 | input: UpdateSubjectAccessRequestInput! 36 | ): SubjectAccessRequest! @requireAuth 37 | deleteSubjectAccessRequest(id: String!): SubjectAccessRequest! @requireAuth 38 | } 39 | ` 40 | -------------------------------------------------------------------------------- /app/api/src/graphql/users.sdl.js: -------------------------------------------------------------------------------- 1 | export const schema = gql` 2 | type User { 3 | id: String! 4 | userName: String! 5 | email: String! 6 | name: String 7 | createdAt: DateTime! 8 | updatedAt: DateTime! 9 | image: String 10 | bio: String 11 | Projects: [Project]! 12 | Project(projectTitle: String): Project 13 | Reaction: [ProjectReaction]! 14 | Comment: [Comment]! 15 | SubjectAccessRequest: [SubjectAccessRequest]! 16 | } 17 | 18 | type Query { 19 | users: [User!]! @requireAuth 20 | user(id: String!): User @skipAuth 21 | userName(userName: String!): User @skipAuth 22 | } 23 | 24 | input CreateUserInput { 25 | userName: String! 26 | email: String! 27 | name: String 28 | image: String 29 | bio: String 30 | } 31 | 32 | input UpdateUserInput { 33 | userName: String 34 | email: String 35 | name: String 36 | image: String 37 | bio: String 38 | } 39 | 40 | type Mutation { 41 | createUser(input: CreateUserInput!): User! @requireAuth 42 | updateUser(id: String!, input: UpdateUserInput!): User! @requireAuth 43 | updateUserByUserName(userName: String!, input: UpdateUserInput!): User! 44 | @requireAuth 45 | deleteUser(id: String!): User! @requireAuth 46 | } 47 | ` 48 | -------------------------------------------------------------------------------- /app/api/src/lib/cloudinary.ts: -------------------------------------------------------------------------------- 1 | import { 2 | v2 as cloudinary, 3 | UploadApiResponse, 4 | UpdateApiOptions, 5 | } from 'cloudinary' 6 | 7 | cloudinary.config({ 8 | cloud_name: 'irevdev', 9 | api_key: process.env.CLOUDINARY_API_KEY, 10 | api_secret: process.env.CLOUDINARY_API_SECRET, 11 | }) 12 | 13 | interface UploadImageArgs { 14 | image64: string 15 | uploadPreset?: string 16 | publicId?: string 17 | invalidate: boolean 18 | } 19 | 20 | export const uploadImage = async ({ 21 | image64, 22 | uploadPreset = 'CadHub_project_images', 23 | publicId, 24 | invalidate = true, 25 | }: UploadImageArgs): Promise => { 26 | const options: UpdateApiOptions = { upload_preset: uploadPreset, invalidate } 27 | if (publicId) { 28 | options.public_id = publicId 29 | } 30 | 31 | return new Promise((resolve, reject) => { 32 | cloudinary.uploader.upload(image64, options, (error, result) => { 33 | if (error) { 34 | reject(error) 35 | return 36 | } 37 | resolve(result) 38 | }) 39 | }) 40 | } 41 | 42 | export const makeSocialPublicIdServer = ( 43 | userName: string, 44 | projectTitle: string 45 | ): string => `u-${userName}-slash-p-${projectTitle}` 46 | -------------------------------------------------------------------------------- /app/api/src/lib/db.js: -------------------------------------------------------------------------------- 1 | // See https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/constructor 2 | // for options. 3 | 4 | import { PrismaClient } from '@prisma/client' 5 | 6 | import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' 7 | 8 | import { logger } from './logger' 9 | 10 | /* 11 | * Instance of the Prisma Client 12 | */ 13 | export const db = new PrismaClient({ 14 | log: emitLogLevels(['info', 'warn', 'error']), 15 | }) 16 | 17 | handlePrismaLogging({ 18 | db, 19 | logger, 20 | logLevels: ['info', 'warn', 'error'], 21 | }) 22 | -------------------------------------------------------------------------------- /app/api/src/lib/discord.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | let inst = null; 4 | if (!process.env.DISCORD_TOKEN || !process.env.DISCORD_CHANNEL_ID) { 5 | console.warn("Discord bot not configured - please set process.env.DISCORD_TOKEN and process.env.DISCORD_CHANNEL_ID to send discord chats"); 6 | } else { 7 | inst = axios.create({ 8 | baseURL: 'https://discord.com/api' 9 | }); 10 | inst.defaults.headers.common['Authorization'] = `Bot ${process.env.DISCORD_TOKEN}` 11 | console.log(`Discord: using API token ${process.env.DISCORD_TOKEN}`); 12 | } 13 | 14 | export async function sendDiscordMessage(text: string, url?: string) { 15 | if (!inst) { 16 | console.error(`Discord: not configured to send message ("${text}")`); 17 | } else { 18 | const API_URL = `/channels/${process.env.DISCORD_CHANNEL_ID}/messages`; 19 | if (url) { 20 | return inst.post(API_URL, { embeds: [{ 21 | title: text, 22 | image: { 23 | url, 24 | }, 25 | }] }); 26 | } else { 27 | return inst.post(API_URL, { 28 | content: text, 29 | }); 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /app/api/src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | import { createLogger } from '@redwoodjs/api/logger' 2 | 3 | /** 4 | * Creates a logger with RedwoodLoggerOptions 5 | * 6 | * These extend and override default LoggerOptions, 7 | * can define a destination like a file or other supported pin log transport stream, 8 | * and sets where or not to show the logger configuration settings (defaults to false) 9 | * 10 | * @param RedwoodLoggerOptions 11 | * 12 | * RedwoodLoggerOptions have 13 | * @param {options} LoggerOptions - defines how to log, such as pretty printing, redaction, and format 14 | * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file 15 | * @param {boolean} showConfig - whether to display logger configuration on initialization 16 | */ 17 | export const logger = createLogger({}) 18 | -------------------------------------------------------------------------------- /app/api/src/lib/sendmail.ts: -------------------------------------------------------------------------------- 1 | import nodemailer, { SendMailOptions } from 'nodemailer' 2 | 3 | export interface SendMailArgs { 4 | to: string 5 | from: SendMailOptions['from'] 6 | subject: string 7 | text: string 8 | } 9 | 10 | interface SuccessResult { 11 | accepted: string[] 12 | rejected: string[] 13 | envelopeTime: number 14 | messageTime: number 15 | messageSize: number 16 | response: string 17 | envelope: { 18 | from: string | false 19 | to: string[] 20 | } 21 | messageId: string 22 | } 23 | 24 | export function sendMail({ 25 | to, 26 | from, 27 | subject, 28 | text, 29 | }: SendMailArgs): Promise { 30 | const transporter = nodemailer.createTransport({ 31 | host: 'smtp.mailgun.org', 32 | port: 587, 33 | secure: false, 34 | tls: { 35 | ciphers: 'SSLv3', 36 | }, 37 | auth: { 38 | user: 'postmaster@mail.cadhub.xyz', 39 | pass: process.env.EMAIL_PASSWORD, 40 | }, 41 | }) 42 | 43 | console.log({ to, from, subject, text }) 44 | 45 | const emailPromise = new Promise((resolve, reject) => { 46 | transporter.sendMail( 47 | { 48 | from, 49 | to, 50 | subject, 51 | text, 52 | }, 53 | (error, info) => { 54 | if (error) { 55 | reject(error) 56 | } else { 57 | resolve(info) 58 | } 59 | } 60 | ) 61 | }) as any as Promise 62 | return emailPromise 63 | } 64 | -------------------------------------------------------------------------------- /app/api/src/services/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/api/src/services/.keep -------------------------------------------------------------------------------- /app/api/src/services/comments/comments.ts: -------------------------------------------------------------------------------- 1 | import { db } from 'src/lib/db' 2 | import { foreignKeyReplacement } from 'src/services/helpers' 3 | 4 | export const comments = () => { 5 | return db.comment.findMany() 6 | } 7 | 8 | export const comment = ({ id }) => { 9 | return db.comment.findUnique({ 10 | where: { id }, 11 | }) 12 | } 13 | 14 | export const createComment = ({ input }) => { 15 | return db.comment.create({ 16 | data: foreignKeyReplacement(input), 17 | }) 18 | } 19 | 20 | export const updateComment = ({ id, input }) => { 21 | return db.comment.update({ 22 | data: foreignKeyReplacement(input), 23 | where: { id }, 24 | }) 25 | } 26 | 27 | export const deleteComment = ({ id }) => { 28 | return db.comment.delete({ 29 | where: { id }, 30 | }) 31 | } 32 | 33 | export const Comment = { 34 | user: (_obj, { root }) => 35 | db.comment.findUnique({ where: { id: root.id } }).user(), 36 | project: (_obj, { root }) => 37 | db.comment.findUnique({ where: { id: root.id } }).project(), 38 | } 39 | -------------------------------------------------------------------------------- /app/api/src/services/email/email.ts: -------------------------------------------------------------------------------- 1 | import { requireAuth } from 'src/lib/auth' 2 | import { sendMail } from 'src/lib/sendmail' 3 | import type { SendMailArgs } from 'src/lib/sendmail' 4 | import { users } from 'src/services/users/users' 5 | 6 | export const sendAllUsersEmail = async ({ input: { body, subject } }) => { 7 | requireAuth({ role: 'admin' }) 8 | const from = { 9 | address: 'news@mail.cadhub.xyz', 10 | name: 'CadHub', 11 | } 12 | const emails: SendMailArgs[] = (await users()).map(({ email }) => ({ 13 | to: email, 14 | from, 15 | subject, 16 | text: body, 17 | })) 18 | const emailPromises = emails.map((email) => sendMail(email)) 19 | const accepted = [] 20 | const rejected = [] 21 | const result = await Promise.allSettled(emailPromises) 22 | result.forEach((result) => { 23 | if (result.status === 'fulfilled') { 24 | accepted.push(result.value.accepted[0]) 25 | } else { 26 | rejected.push(result.reason) 27 | } 28 | }) 29 | await sendMail({ 30 | to: 'k.hutten@protonmail.ch', 31 | from, 32 | subject: `All users email report`, 33 | text: JSON.stringify( 34 | { 35 | accepted, 36 | rejected, 37 | originalEmailList: emails, 38 | }, 39 | null, 40 | 2 41 | ), 42 | }) 43 | 44 | return { accepted, rejected } 45 | } 46 | -------------------------------------------------------------------------------- /app/api/src/services/socialCards/socialCards.ts: -------------------------------------------------------------------------------- 1 | import { ResolverArgs, BeforeResolverSpecType } from '@redwoodjs/graphql-server' 2 | import type { Prisma } from '@prisma/client' 3 | 4 | import { db } from 'src/lib/db' 5 | import { requireAuth } from 'src/lib/auth' 6 | 7 | // Used when the environment variable REDWOOD_SECURE_SERVICES=1 8 | export const beforeResolver = (rules: BeforeResolverSpecType) => { 9 | rules.add(requireAuth) 10 | } 11 | 12 | export const socialCards = () => { 13 | return db.socialCard.findMany() 14 | } 15 | 16 | export const socialCard = ({ id }: Prisma.SocialCardWhereUniqueInput) => { 17 | return db.socialCard.findUnique({ 18 | where: { id }, 19 | }) 20 | } 21 | 22 | export const SocialCard = { 23 | project: (_obj, { root }: ResolverArgs>) => 24 | db.socialCard.findUnique({ where: { id: root.id } }).project(), 25 | } 26 | -------------------------------------------------------------------------------- /app/api/src/services/subjectAccessRequests/subjectAccessRequests.js: -------------------------------------------------------------------------------- 1 | import { db } from 'src/lib/db' 2 | import { requireAuth } from 'src/lib/auth' 3 | import { foreignKeyReplacement } from 'src/services/helpers' 4 | 5 | export const subjectAccessRequests = () => { 6 | requireAuth({ role: 'admin' }) 7 | return db.subjectAccessRequest.findMany() 8 | } 9 | 10 | export const subjectAccessRequest = ({ id }) => { 11 | requireAuth({ role: 'admin' }) 12 | return db.subjectAccessRequest.findUnique({ 13 | where: { id }, 14 | }) 15 | } 16 | 17 | export const createSubjectAccessRequest = ({ input }) => { 18 | requireAuth({ role: 'admin' }) 19 | return db.subjectAccessRequest.create({ 20 | data: foreignKeyReplacement(input), 21 | }) 22 | } 23 | 24 | export const updateSubjectAccessRequest = ({ id, input }) => { 25 | requireAuth({ role: 'admin' }) 26 | return db.subjectAccessRequest.update({ 27 | data: foreignKeyReplacement(input), 28 | where: { id }, 29 | }) 30 | } 31 | 32 | export const deleteSubjectAccessRequest = ({ id }) => { 33 | requireAuth({ role: 'admin' }) 34 | return db.subjectAccessRequest.delete({ 35 | where: { id }, 36 | }) 37 | } 38 | 39 | export const SubjectAccessRequest = { 40 | user: (_obj, { root }) => 41 | db.subjectAccessRequest.findUnique({ where: { id: root.id } }).user(), 42 | } 43 | -------------------------------------------------------------------------------- /app/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "allowJs": true, 5 | "esModuleInterop": true, 6 | "target": "esnext", 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "baseUrl": "./", 10 | "paths": { 11 | "src/*": ["./src/*"] 12 | }, 13 | "typeRoots": ["../node_modules/@types", "./node_modules/@types"], 14 | "types": ["jest"] 15 | }, 16 | "include": ["src", "../.redwood/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /app/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@redwoodjs/core/config/babel-preset'], 3 | } 4 | -------------------------------------------------------------------------------- /app/graphql.config.js: -------------------------------------------------------------------------------- 1 | const { getPaths } = require('@redwoodjs/internal') 2 | 3 | module.exports = { 4 | schema: getPaths().generated.schema, 5 | } 6 | -------------------------------------------------------------------------------- /app/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn rw deploy netlify" 3 | publish = "web/dist" 4 | functions = "api/dist/functions" 5 | 6 | [dev] 7 | # To use [Netlify Dev](https://www.netlify.com/products/dev/), 8 | # install netlify-cli from https://docs.netlify.com/cli/get-started/#installation 9 | # and then use netlify link https://docs.netlify.com/cli/get-started/#link-and-unlink-sites 10 | # to connect your local project to a site already on Netlify 11 | # then run netlify dev and our app will be accessible on the port specified below 12 | framework = "redwoodjs" 13 | # Set targetPort to the [web] side port as defined in redwood.toml 14 | targetPort = 8910 15 | # Point your browser to this port to access your RedwoodJS app 16 | port = 8888 17 | 18 | [[redirects]] 19 | from = "/*" 20 | to = "/index.html" 21 | status = 200 22 | 23 | [context.deploy-preview.environment] 24 | CAD_LAMBDA_BASE_URL = "https://t7wdlz8ztf.execute-api.us-east-1.amazonaws.com/dev2" 25 | 26 | [[plugins]] 27 | package = "@sentry/netlify-build-plugin" 28 | 29 | [plugins.inputs] 30 | sentryOrg = "kurt" 31 | sentryProject = "kurt" 32 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": { 4 | "packages": [ 5 | "api", 6 | "web" 7 | ] 8 | }, 9 | "scripts": { 10 | "cad": "yarn rw build api && docker-compose --file ./api/src/docker/docker-compose.yml up --build", 11 | "cad-r": "yarn rw build api && docker-compose --file ./api/src/docker/docker-compose.yml restart", 12 | "aws-emulate": "nodemon ./api/src/docker/aws-emulator.js" 13 | }, 14 | "devDependencies": { 15 | "@redwoodjs/core": "^0.38.1" 16 | }, 17 | "eslintConfig": { 18 | "extends": "@redwoodjs/eslint-config", 19 | "rules": { 20 | "react/no-unescaped-entities": [ 21 | "error", 22 | { 23 | "forbid": [ 24 | ">", 25 | "}", 26 | "\"" 27 | ] 28 | } 29 | ] 30 | } 31 | }, 32 | "engines": { 33 | "node": ">=14.x <=16.x", 34 | "yarn": ">=1.15" 35 | }, 36 | "prisma": { 37 | "seed": "yarn rw exec seed" 38 | } 39 | } -------------------------------------------------------------------------------- /app/prettier.config.js: -------------------------------------------------------------------------------- 1 | // https://prettier.io/docs/en/options.html 2 | module.exports = { 3 | trailingComma: 'es5', 4 | bracketSpacing: true, 5 | tabWidth: 2, 6 | semi: false, 7 | singleQuote: true, 8 | arrowParens: 'always', 9 | overrides: [ 10 | { 11 | files: 'Routes.js', 12 | options: { 13 | printWidth: 200, 14 | }, 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /app/redwood.toml: -------------------------------------------------------------------------------- 1 | # This file contains the configuration settings for your Redwood app. 2 | # This file is also what makes your Redwood app a Redwood app. 3 | # If you remove it and try to run `yarn rw dev`, you'll get an error. 4 | # 5 | # For the full list of options, see the "App Configuration: redwood.toml" doc: 6 | # https://redwoodjs.com/docs/app-configuration-redwood-toml 7 | 8 | [web] 9 | port = 8910 10 | apiUrl = "/.netlify/functions" 11 | includeEnvironmentVariables = [ 12 | 'GOOGLE_ANALYTICS_ID', 13 | 'CLOUDINARY_API_KEY', 14 | 'CLOUDINARY_API_SECRET', 15 | 'CAD_LAMBDA_BASE_URL', 16 | 'SENTRY_DSN', 17 | 'SENTRY_AUTH_TOKEN', 18 | 'SENTRY_ORG', 19 | 'SENTRY_PROJECT', 20 | 'EMAIL_PASSWORD', 21 | ] 22 | # experimentalFastRefresh = true # this seems to break cascadeStudio 23 | [api] 24 | port = 8911 25 | schemaPath = "./api/db/schema.prisma" 26 | [browser] 27 | open = true 28 | 29 | [experimental] 30 | esbuild = true 31 | 32 | -------------------------------------------------------------------------------- /app/web/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: "../babel.config.js" } 2 | -------------------------------------------------------------------------------- /app/web/config/postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('postcss-import'), 6 | require('tailwindcss')(path.resolve(__dirname, 'tailwind.config.js')), 7 | require('autoprefixer'), 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /app/web/config/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (config, { env }) => { 3 | config.plugins.forEach((plugin) => { 4 | if (plugin.constructor.name === 'HtmlWebpackPlugin') { 5 | plugin.userOptions.favicon = './src/favicon.svg' 6 | } 7 | }) 8 | config.module.rules.push({ 9 | test: /\.(md|jscad\.js|py|scad|curv)$/i, 10 | use: 'raw-loader', 11 | }); 12 | return config 13 | } 14 | -------------------------------------------------------------------------------- /app/web/config/worker-loader.d.ts: -------------------------------------------------------------------------------- 1 | declare module "worker-loader!*" { 2 | // You need to change `Worker`, if you specified a different value for the `workerType` option 3 | class WebpackWorker extends Worker { 4 | constructor(); 5 | } 6 | 7 | // Uncomment this if you set the `esModule` option to `false` 8 | // export = WebpackWorker; 9 | export default WebpackWorker; 10 | } 11 | -------------------------------------------------------------------------------- /app/web/identity-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": "signup", 3 | "instance_id": "403b7d63-17f9-48f1-a85f-3d6b41c7dad1", 4 | "user": { 5 | "id": "641222ee-3e61-4253-8c11-9f764779bcc5", 6 | "aud": "", 7 | "role": "", 8 | "email": "k.hutten2@protonmail.ch", 9 | "confirmation_sent_at": "2020-10-19T18:09:01Z", 10 | "app_metadata": { "provider": "email" }, 11 | "user_metadata": { "full_name": "sick_dog", "userName": "hi bob" }, 12 | "created_at": "2020-10-19T18:09:01Z", 13 | "updated_at": "2020-10-19T18:09:01Z" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/web/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@redwoodjs/testing/config/jest/api') 2 | -------------------------------------------------------------------------------- /app/web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": ["./src/*"] 6 | }, 7 | "jsx": "preserve" 8 | }, 9 | "include": ["src/**/*", "../.redwood/index.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /app/web/public/default-social-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/web/public/default-social-image.jpg -------------------------------------------------------------------------------- /app/web/public/hinge.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/web/public/hinge.stl -------------------------------------------------------------------------------- /app/web/public/pumpjack.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/web/public/pumpjack.stl -------------------------------------------------------------------------------- /app/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /app/web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { AuthProvider } from '@redwoodjs/auth' 2 | import GoTrue from 'gotrue-js' 3 | 4 | import { RedwoodProvider } from '@redwoodjs/web' 5 | import FatalErrorBoundary from 'src/components/FatalErrorBoundary/FatalErrorBoundary' 6 | import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' 7 | import FatalErrorPage from 'src/pages/FatalErrorPage' 8 | import { createTheme } from '@material-ui/core/styles' 9 | import { ThemeProvider } from '@material-ui/styles' 10 | import ReactGA from 'react-ga' 11 | 12 | ReactGA.initialize(process.env.GOOGLE_ANALYTICS_ID) 13 | 14 | import Routes from 'src/Routes' 15 | 16 | import './font-imports.css' 17 | import './scaffold.css' 18 | import './index.css' 19 | 20 | const goTrueClient = new GoTrue({ 21 | APIUrl: 'https://cadhub.xyz/.netlify/identity', 22 | setCookie: true, 23 | }) 24 | 25 | const theme = createTheme({ 26 | palette: { 27 | type: 'dark', 28 | primary: { 29 | light: '#C99DFF', 30 | main: '#A663FA', 31 | dark: '#3B0480', 32 | }, 33 | }, 34 | }) 35 | 36 | const App = () => ( 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | 50 | export default App 51 | -------------------------------------------------------------------------------- /app/web/src/components/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/web/src/components/.keep -------------------------------------------------------------------------------- /app/web/src/components/AdminProjectsCell/AdminProjectsCell.tsx: -------------------------------------------------------------------------------- 1 | import { Link, routes } from '@redwoodjs/router' 2 | 3 | import AdminProjects from 'src/components/AdminProjects/AdminProjects' 4 | 5 | export const QUERY = gql` 6 | query PROJECTS_ADMIN { 7 | projects { 8 | id 9 | title 10 | description 11 | code 12 | mainImage 13 | createdAt 14 | updatedAt 15 | userId 16 | deleted 17 | user { 18 | userName 19 | } 20 | } 21 | } 22 | ` 23 | 24 | export const Loading = () =>
Loading...
25 | 26 | export const Empty = () => { 27 | return ( 28 |
29 | {'No projects yet. '} 30 | 31 | {'Create one?'} 32 | 33 |
34 | ) 35 | } 36 | 37 | export const Success = ({ projects }) => { 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /app/web/src/components/Button/Button.js: -------------------------------------------------------------------------------- 1 | import { getActiveClasses } from 'get-active-classes' 2 | import Svg from 'src/components/Svg' 3 | 4 | const Button = ({ 5 | onClick, 6 | iconName, 7 | children, 8 | className, 9 | shouldAnimateHover, 10 | disabled, 11 | type, 12 | }) => { 13 | return ( 14 | 34 | ) 35 | } 36 | 37 | export default Button 38 | -------------------------------------------------------------------------------- /app/web/src/components/Button/Button.stories.js: -------------------------------------------------------------------------------- 1 | import Button from './Button' 2 | 3 | export const generated = () => { 4 | return ( 5 | <> 6 | button with icon 7 | 8 | 9 | ) 10 | } 11 | 12 | export default { title: 'Components/Button' } 13 | -------------------------------------------------------------------------------- /app/web/src/components/Button/Button.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import Button from './Button' 4 | 5 | describe('Button', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render( 19 | 31 | 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | export default ConfirmDialog 39 | -------------------------------------------------------------------------------- /app/web/src/components/ConfirmDialog/ConfirmDialog.stories.js: -------------------------------------------------------------------------------- 1 | import ConfirmDialog from './ConfirmDialog' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/ConfirmDialog' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/ConfirmDialog/ConfirmDialog.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import ConfirmDialog from './ConfirmDialog' 4 | 5 | describe('ConfirmDialog', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/Customizer/customizerConverter.ts: -------------------------------------------------------------------------------- 1 | // CadHub 2 | 3 | type CadhubTypeNames = 'number' | 'string' | 'boolean' 4 | type CadhubInputNames = 5 | | 'default-number' 6 | | 'default-string' 7 | | 'default-boolean' 8 | | 'choice-string' 9 | | 'choice-number' 10 | 11 | export interface CadhubStringOption { 12 | name: string 13 | value: string 14 | } 15 | 16 | export interface CadhubNumberOption { 17 | name: string 18 | value: number 19 | } 20 | 21 | interface CadhubParamBase { 22 | type: CadhubTypeNames 23 | caption: string 24 | name: string 25 | input: CadhubInputNames 26 | } 27 | 28 | export interface CadhubStringParam extends CadhubParamBase { 29 | type: 'string' 30 | input: 'default-string' 31 | initial: string 32 | placeholder?: string 33 | maxLength?: number 34 | } 35 | export interface CadhubBooleanParam extends CadhubParamBase { 36 | type: 'boolean' 37 | input: 'default-boolean' 38 | initial?: boolean 39 | } 40 | export interface CadhubNumberParam extends CadhubParamBase { 41 | type: 'number' 42 | input: 'default-number' 43 | initial: number 44 | min?: number 45 | max?: number 46 | step?: number 47 | decimal?: number 48 | } 49 | 50 | export interface CadhubStringChoiceParam extends CadhubParamBase { 51 | type: 'string' 52 | input: 'choice-string' 53 | initial: string 54 | options: Array 55 | } 56 | export interface CadhubNumberChoiceParam extends CadhubParamBase { 57 | type: 'number' 58 | input: 'choice-number' 59 | initial: number 60 | options: Array 61 | } 62 | 63 | export type CadhubParams = 64 | | CadhubStringParam 65 | | CadhubBooleanParam 66 | | CadhubNumberParam 67 | | CadhubStringChoiceParam 68 | | CadhubNumberChoiceParam 69 | -------------------------------------------------------------------------------- /app/web/src/components/DelayedPingAnimation/DelayedPingAnimation.tsx: -------------------------------------------------------------------------------- 1 | let timeoutId = 0 2 | const DelayedPingAnimation = ({ 3 | isLoading: isLoading, 4 | }: { 5 | isLoading: boolean 6 | }) => { 7 | const [showLoading, setShowLoading] = React.useState(false) 8 | React.useEffect(() => { 9 | if (!isLoading && showLoading) { 10 | setShowLoading(isLoading) 11 | clearTimeout(timeoutId) 12 | } else if (isLoading && !showLoading) { 13 | timeoutId = setTimeout(() => { 14 | setShowLoading(isLoading) 15 | }, 300) as unknown as number 16 | } else if (!isLoading) { 17 | setShowLoading(isLoading) 18 | clearTimeout(timeoutId) 19 | } 20 | }, [isLoading]) 21 | 22 | if (showLoading && isLoading) 23 | return ( 24 |
25 |
26 |
27 | ) 28 | return null 29 | } 30 | 31 | export default DelayedPingAnimation 32 | -------------------------------------------------------------------------------- /app/web/src/components/EditUserCell/EditUserCell.tsx: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@redwoodjs/web' 2 | import { toast } from '@redwoodjs/web/toast' 3 | import { navigate, routes } from '@redwoodjs/router' 4 | 5 | import UserProfile from 'src/components/UserProfile' 6 | 7 | export const QUERY = gql` 8 | query FIND_USER_BY_USERNAME($userName: String!) { 9 | user: userName(userName: $userName) { 10 | id 11 | userName 12 | name 13 | createdAt 14 | updatedAt 15 | image 16 | bio 17 | } 18 | } 19 | ` 20 | 21 | const UPDATE_USER_MUTATION = gql` 22 | mutation UpdateUserMutation($userName: String!, $input: UpdateUserInput!) { 23 | updateUserByUserName(userName: $userName, input: $input) { 24 | id 25 | userName 26 | bio 27 | name 28 | image 29 | } 30 | } 31 | ` 32 | 33 | export const Loading = () =>
Loading...
34 | 35 | export const Empty = () =>
Empty
36 | 37 | export const Failure = ({ error }) =>
Error: {error.message}
38 | 39 | export const Success = ({ user, refetch, variables: { isEditable } }) => { 40 | const [updateUser, { loading, error }] = useMutation(UPDATE_USER_MUTATION, { 41 | onCompleted: ({ updateUserByUserName }) => { 42 | navigate(routes.user({ userName: updateUserByUserName.userName })) 43 | toast.success('User updated.') 44 | }, 45 | }) 46 | 47 | const onSave = async (userName, input) => { 48 | await updateUser({ variables: { userName, input } }) 49 | refetch() 50 | } 51 | 52 | return ( 53 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /app/web/src/components/EmbedProject/EmbedProject.tsx: -------------------------------------------------------------------------------- 1 | import Seo from 'src/components/Seo/Seo' 2 | import IdeViewer from 'src/components/IdeViewer/IdeViewer' 3 | import { useIdeState } from 'src/helpers/hooks/useIdeState' 4 | import type { Project } from 'src/components/EmbedProjectCell/EmbedProjectCell' 5 | import { IdeContext } from 'src/helpers/hooks/useIdeContext' 6 | import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize' 7 | import { useEffect } from 'react' 8 | 9 | 10 | interface Props { 11 | project?: Project 12 | } 13 | 14 | const EmbedProject = ({ project }: Props) => { 15 | const [state, thunkDispatch] = useIdeState() 16 | const { viewerDomRef, handleViewerSizeUpdate } = use3dViewerResize() 17 | 18 | useEffect(() => { 19 | handleViewerSizeUpdate() 20 | }, []) 21 | 22 | return ( 23 |
24 | 25 | 26 | 27 |
28 | ) 29 | } 30 | 31 | export default EmbedProject 32 | -------------------------------------------------------------------------------- /app/web/src/components/EmbedProjectCell/EmbedProjectCell.mock.ts: -------------------------------------------------------------------------------- 1 | // Define your own mock data here: 2 | export const standard = (/* vars, { ctx, req } */) => ({ 3 | ideProject: { 4 | id: 42, 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /app/web/src/components/EmbedProjectCell/EmbedProjectCell.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Loading, Empty, Success } from './EmbedProjectCell' 2 | import { standard } from './EmbedProjectCell.mock' 3 | 4 | export const loading = () => { 5 | return Loading ? : null 6 | } 7 | 8 | export const empty = () => { 9 | return Empty ? : null 10 | } 11 | 12 | export const success = () => { 13 | return Success ? : null 14 | } 15 | 16 | export default { title: 'Cells/IdeProjectCell' } 17 | -------------------------------------------------------------------------------- /app/web/src/components/EmbedProjectCell/EmbedProjectCell.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@redwoodjs/testing' 2 | import { Loading, Empty, Success } from './EmbedProjectCell' 3 | import { standard } from './EmbedProjectCell.mock' 4 | 5 | describe('IdeProjectCell', () => { 6 | test('Loading renders successfully', () => { 7 | render() 8 | // Use screen.debug() to see output 9 | expect(screen.getByText('Loading...')).toBeInTheDocument() 10 | }) 11 | 12 | test('Empty renders successfully', async () => { 13 | render() 14 | expect(screen.getByText('Empty')).toBeInTheDocument() 15 | }) 16 | 17 | test('Success renders successfully', async () => { 18 | render() 19 | expect(screen.getByText(/42/i)).toBeInTheDocument() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /app/web/src/components/EmbedProjectCell/EmbedProjectCell.tsx: -------------------------------------------------------------------------------- 1 | import { useIdeState } from 'src/helpers/hooks/useIdeState' 2 | import { IdeContext } from 'src/helpers/hooks/useIdeContext' 3 | import EmbedViewer from '../EmbedViewer/EmbedViewer' 4 | import { QUERY as IdeQuery } from 'src/components/IdeProjectCell' 5 | 6 | export const QUERY = IdeQuery 7 | export interface Project { 8 | id: string 9 | title: string 10 | code: string 11 | description: string 12 | mainImage: string 13 | createdAt: string 14 | cadPackage: 'openscad' | 'cadquery' 15 | user: { 16 | id: string 17 | userName: string 18 | image: string 19 | } 20 | } 21 | 22 | export const Loading = () =>
Loading...
23 | 24 | export const Empty = () =>
Project not found
25 | 26 | interface SaveCodeArgs { 27 | input: any 28 | id: string 29 | isFork: boolean 30 | } 31 | 32 | export const Success = ({ 33 | project, 34 | refetch, 35 | }: { 36 | project: Project 37 | refetch: any 38 | }) => { 39 | const [state, thunkDispatch] = useIdeState() 40 | 41 | 42 | return ( 43 | 44 | 45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /app/web/src/components/EmbedViewer/EmbedViewer.tsx: -------------------------------------------------------------------------------- 1 | import { useIdeInit } from 'src/components/EncodedUrl/helpers' 2 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 3 | import IdeViewer from 'src/components/IdeViewer/IdeViewer' 4 | import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize' 5 | import CadPackage from '../CadPackage/CadPackage' 6 | import LogoType from '../LogoType/LogoType' 7 | import { Link, routes } from '@redwoodjs/router' 8 | 9 | function EmbedViewer() { 10 | const { state, project } = useIdeContext() 11 | useIdeInit(project?.cadPackage, project?.code || state?.code, "viewer") 12 | const { viewerDomRef } = use3dViewerResize() 13 | 14 | return ( 15 |
16 | 17 |
18 |

19 | {project?.title.replace(/-/g, ' ')} 20 |

21 |

by @{ project?.user?.userName }

22 |

built with

23 |
24 |
25 | View on 29 |
30 |
31 | ) 32 | } 33 | 34 | export default EmbedViewer 35 | -------------------------------------------------------------------------------- /app/web/src/components/EncodedUrl/FullScriptEncoding.tsx: -------------------------------------------------------------------------------- 1 | import { makeEncodedLink } from './helpers' 2 | import { copyTextToClipboard } from 'src/helpers/clipboard' 3 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 4 | 5 | const FullScriptEncoding = () => { 6 | const { state } = useIdeContext() 7 | const encodedLink = makeEncodedLink(state.code) 8 | return ( 9 |
10 |

11 | Encodes your CodeCad script into a URL so that you can share your work 12 |

13 | 18 | 24 |
25 | ) 26 | } 27 | 28 | export default FullScriptEncoding 29 | -------------------------------------------------------------------------------- /app/web/src/components/FatalErrorBoundary/FatalErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { FatalErrorBoundary as FatalErrorBoundaryBase } from '@redwoodjs/web' 2 | import * as Sentry from '@sentry/browser' 3 | 4 | class FatalErrorBoundary extends FatalErrorBoundaryBase { 5 | async componentDidCatch(error, errorInfo) { 6 | // debug netlify prerender code below 7 | // const div = document.createElement('div') 8 | // div.innerHTML = JSON.stringify(error) 9 | // document.body.append(div) 10 | 11 | /* More debug explanation. 12 | If there's an error in netlify's prerendering service, 13 | we don't have access to the log so we have to spin it up locally to check. 14 | This can be with the following commands 15 | ``` 16 | $ git clone https://github.com/netlify/prerender.git 17 | $ cd prerender 18 | ``` 19 | comment out the lines `server.use(require("./lib/plugins/basicAuth"));` and `server.use(require("./lib/plugins/s3HtmlCache"));` in `server.js` 20 | then 21 | ``` 22 | $ npm install 23 | $ npm start 24 | ``` 25 | This will spin up the service on port 3000, prerendering can than be tested with 26 | http://localhost:3000/https://cadhub.xyz 27 | or 28 | http://localhost:3000/http://localhost:8910/ 29 | where the second url is the route you want to test. 30 | However we don't have access to the console since it's run by a separate chrome instance, 31 | so instead errors are put into the DOM 32 | */ 33 | Sentry.withScope((scope) => { 34 | scope.setExtras(errorInfo) 35 | Sentry.captureException(error) 36 | }) 37 | } 38 | } 39 | export default FatalErrorBoundary 40 | -------------------------------------------------------------------------------- /app/web/src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Link, routes } from '@redwoodjs/router' 2 | import OutBound from 'src/components/OutBound' 3 | 4 | const Footer = () => { 5 | return ( 6 |
7 |
8 | 9 | Github 10 | 11 | 12 | Docs 13 | 14 | 18 | Road Map 19 | 20 | 21 | Blog 22 | 23 | 24 | Code of Conduct 25 | 26 | Privacy Policy 27 |
28 |
29 | ) 30 | } 31 | 32 | export default Footer 33 | -------------------------------------------------------------------------------- /app/web/src/components/Gravatar/Gravatar.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Image as CloudinaryImage } from 'cloudinary-react' 3 | 4 | interface Props { 5 | image: string 6 | className?: string 7 | size?: number 8 | } 9 | 10 | const Gravatar = ({ image, size = 40, className = '' }: Props) => { 11 | return ( 12 |
18 | 24 |
25 | ) 26 | } 27 | 28 | export default Gravatar 29 | -------------------------------------------------------------------------------- /app/web/src/components/IdeConsole/IdeConsole.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 3 | 4 | const IdeConsole = () => { 5 | const { state } = useIdeContext() 6 | useEffect(() => { 7 | const element = document.querySelector('.console-tile .mosaic-window-body') 8 | if (element) { 9 | element.scrollTop = element.scrollHeight - element.clientHeight 10 | } 11 | }, [state.consoleMessages]) 12 | 13 | return ( 14 |
15 |
16 | {state.consoleMessages?.map(({ type, message, time }, index) => ( 17 |
21 |             
22 | {time?.toLocaleString()} 23 |
24 |
25 | {(message || '').split('\n').map((line, index) => { 26 | return ( 27 |
28 | {line.startsWith('ECHO:') ? ( 29 | 30 | ECHO:{' '} 31 | 32 | {line.slice(6)} 33 | 34 | 35 | ) : ( 36 | line 37 | )} 38 |
39 | ) 40 | })} 41 |
42 |
43 | ))} 44 |
45 |
46 | ) 47 | } 48 | 49 | export default IdeConsole 50 | -------------------------------------------------------------------------------- /app/web/src/components/IdeProjectCell/IdeProjectCell.mock.ts: -------------------------------------------------------------------------------- 1 | // Define your own mock data here: 2 | export const standard = (/* vars, { ctx, req } */) => ({ 3 | ideProject: { 4 | id: 42, 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /app/web/src/components/IdeProjectCell/IdeProjectCell.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Loading, Empty, Success } from './IdeProjectCell' 2 | import { standard } from './IdeProjectCell.mock' 3 | 4 | export const loading = () => { 5 | return Loading ? : null 6 | } 7 | 8 | export const empty = () => { 9 | return Empty ? : null 10 | } 11 | 12 | export const success = () => { 13 | return Success ? : null 14 | } 15 | 16 | export default { title: 'Cells/IdeProjectCell' } 17 | -------------------------------------------------------------------------------- /app/web/src/components/IdeProjectCell/IdeProjectCell.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@redwoodjs/testing' 2 | import { Loading, Empty, Success } from './IdeProjectCell' 3 | import { standard } from './IdeProjectCell.mock' 4 | 5 | describe('IdeProjectCell', () => { 6 | test('Loading renders successfully', () => { 7 | render() 8 | // Use screen.debug() to see output 9 | expect(screen.getByText('Loading...')).toBeInTheDocument() 10 | }) 11 | 12 | test('Empty renders successfully', async () => { 13 | render() 14 | expect(screen.getByText('Empty')).toBeInTheDocument() 15 | }) 16 | 17 | test('Success renders successfully', async () => { 18 | render() 19 | expect(screen.getByText(/42/i)).toBeInTheDocument() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /app/web/src/components/IdeViewer/IdeViewer.tsx: -------------------------------------------------------------------------------- 1 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 2 | import { requestRender } from 'src/helpers/hooks/useIdeState' 3 | import { PureIdeViewer } from './PureIdeViewer' 4 | 5 | const IdeViewer = ({ 6 | handleOwnCamera = false, 7 | isMinimal = false, 8 | }: { 9 | handleOwnCamera?: boolean, 10 | isMinimal?: boolean, 11 | }) => { 12 | const { state, thunkDispatch } = useIdeContext() 13 | const dataType = state.objectData?.type 14 | const artifact = state.objectData?.data 15 | const ideType = state.ideType 16 | 17 | const onInit = (threeInstance) => { 18 | thunkDispatch({ type: 'setThreeInstance', payload: threeInstance }) 19 | } 20 | const onCameraChange = (camera) => { 21 | if (handleOwnCamera) { 22 | return 23 | } 24 | thunkDispatch({ 25 | type: 'updateCamera', 26 | payload: { camera }, 27 | }) 28 | thunkDispatch((dispatch, getState) => { 29 | const state = getState() 30 | if ( 31 | ['png', 'INIT'].includes(state?.objectData?.type) && 32 | (ideType === 'openscad' || 33 | state?.objectData?.type === 'INIT' || 34 | !state?.objectData?.type) 35 | ) { 36 | dispatch({ type: 'setLoading' }) 37 | requestRender({ 38 | state, 39 | dispatch, 40 | camera, 41 | viewAll: state?.objectData?.type === 'INIT', 42 | }) 43 | } 44 | }) 45 | } 46 | 47 | return ( 48 | 58 | ) 59 | } 60 | 61 | export default IdeViewer 62 | -------------------------------------------------------------------------------- /app/web/src/components/IdeViewer/dullFrontLitMetal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/web/src/components/IdeViewer/dullFrontLitMetal.png -------------------------------------------------------------------------------- /app/web/src/components/IdeWrapper/IdeWrapper.test.js: -------------------------------------------------------------------------------- 1 | // import { githubSafe } from './IdeToolbarNew.js' 2 | // TODO jest doesn't like ECMAScript modules and is failing further down in the tree because three ES modules 3 | // Need to update config 4 | 5 | describe('githubSafe', () => { 6 | const rawUrl = 7 | 'https://raw.githubusercontent.com/aaevan/openscad_objects/main/fire_tablet_bottom_corner.scad' 8 | it('It transforms non-raw url to raw urls', () => { 9 | expect(1).toBe(1) 10 | }) 11 | // it('It transforms non-raw url to raw urls', () => { 12 | // expect( 13 | // githubSafe( 14 | // 'https://github.com/aaevan/openscad_objects/blob/main/fire_tablet_bottom_corner.scad' 15 | // ) 16 | // ).toBe(rawUrl) 17 | // }) 18 | // it('It leaves raw urls untouched', () => { 19 | // expect(githubSafe(rawUrl)).toBe(rawUrl) 20 | // }) 21 | }) 22 | -------------------------------------------------------------------------------- /app/web/src/components/IdeWrapper/useRender.ts: -------------------------------------------------------------------------------- 1 | import { makeCodeStoreKey, requestRender } from 'src/helpers/hooks/useIdeState' 2 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 3 | 4 | export const useRender = () => { 5 | const { state, thunkDispatch } = useIdeContext() 6 | return (disableParams = false) => { 7 | thunkDispatch((dispatch, getState) => { 8 | const state = getState() 9 | dispatch({ type: 'setLoading' }) 10 | requestRender({ 11 | state, 12 | dispatch, 13 | parameters: disableParams ? {} : state.currentParameters, 14 | }) 15 | }) 16 | localStorage.setItem(makeCodeStoreKey(state.ideType), state.code) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/web/src/components/IdeWrapper/useSaveCode.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@redwoodjs/web' 2 | import { toast } from '@redwoodjs/web/toast' 3 | import { useState } from 'react' 4 | import type { Prisma } from '@prisma/client' 5 | import { useAuth } from '@redwoodjs/auth' 6 | 7 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 8 | import { UPDATE_PROJECT_MUTATION_IDE } from 'src/components/IdeProjectCell/IdeProjectCell' 9 | 10 | export const useSaveCode = () => { 11 | const { currentUser } = useAuth() 12 | const { project } = useIdeContext() 13 | const [updateProject, { error }] = useMutation(UPDATE_PROJECT_MUTATION_IDE) 14 | const [nowError, setNowError] = useState(false) 15 | if (error && !nowError) { 16 | toast.success('problem updating updating project') 17 | } 18 | if (!!error !== nowError) { 19 | setNowError(!!error) 20 | } 21 | if (!currentUser || project?.user?.id !== currentUser?.sub) { 22 | return () => {} 23 | } 24 | return (input: Prisma.ProjectUpdateInput) => { 25 | updateProject({ variables: { id: project.id, input } }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/web/src/components/ImageUploader/ImageUploader.stories.js: -------------------------------------------------------------------------------- 1 | import ImageUploader from './ImageUploader' 2 | 3 | export const generated = () => { 4 | return ( 5 | <> 6 |

AspectRatio:1, no initial image, editable

7 | 9 | console.log(cloudinaryPublicId) 10 | } 11 | aspectRatio={1} 12 | isEditable={true} 13 | className={'bg-red-400 rounded-half rounded-br-xl'} 14 | /> 15 |

AspectRatio 16:9, no initial image, editable

16 | 18 | console.log(cloudinaryPublicId) 19 | } 20 | aspectRatio={16 / 9} 21 | isEditable={true} 22 | className={'bg-red-400 rounded-xl'} 23 | imageUrl="CadHub/inakek2urbreynblzhgt" 24 | /> 25 |

AspectRatio:1, no initial image, NOT editable

26 | 28 | console.log(cloudinaryPublicId) 29 | } 30 | aspectRatio={1} 31 | className={'rounded-half rounded-br-xl'} 32 | /> 33 |

AspectRatio ,16:9 no initial image, NOT editable

34 | 36 | console.log(cloudinaryPublicId) 37 | } 38 | aspectRatio={16 / 9} 39 | className={'rounded-xl'} 40 | imageUrl="CadHub/inakek2urbreynblzhgt" 41 | /> 42 | 43 | ) 44 | } 45 | 46 | export default { title: 'Components/ImageUploader' } 47 | -------------------------------------------------------------------------------- /app/web/src/components/ImageUploader/ImageUploader.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import ImageUploader from './ImageUploader' 4 | 5 | describe('ImageUploader', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/InputText/InputText.js: -------------------------------------------------------------------------------- 1 | import { getActiveClasses } from 'get-active-classes' 2 | 3 | const InputText = ({ 4 | value, 5 | isEditable, 6 | onChange, 7 | className, 8 | isInvalid = false, 9 | }) => { 10 | return ( 11 | <> 12 |
19 |
25 | 32 |
33 | 40 | {value} 41 | 42 | 43 | ) 44 | } 45 | 46 | export default InputText 47 | -------------------------------------------------------------------------------- /app/web/src/components/InputText/InputText.stories.js: -------------------------------------------------------------------------------- 1 | import InputText from './InputText' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/InputText' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/InputText/InputText.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import InputText from './InputText' 4 | 5 | describe('InputText', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/InputTextForm/InputTextForm.stories.js: -------------------------------------------------------------------------------- 1 | import InputTextForm from './InputTextForm' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/InputTextForm' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/InputTextForm/InputTextForm.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import InputTextForm from './InputTextForm' 4 | 5 | describe('InputTextForm', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/InputTextForm/InputTextForm.tsx: -------------------------------------------------------------------------------- 1 | import { getActiveClasses } from 'get-active-classes' 2 | import { TextField, FieldError } from '@redwoodjs/forms' 3 | import { useFormContext } from 'react-hook-form' 4 | 5 | const InputText = ({ type = 'text', className, name, validation }) => { 6 | const { 7 | formState: { errors }, 8 | } = useFormContext() 9 | return ( 10 | <> 11 |
12 | 16 | 26 |
27 | 28 | ) 29 | } 30 | 31 | export default InputText 32 | -------------------------------------------------------------------------------- /app/web/src/components/LandingSection/LandingSection.stories.js: -------------------------------------------------------------------------------- 1 | import LandingSection from './LandingSection' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/LandingSection' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/LandingSection/LandingSection.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import LandingSection from './LandingSection' 4 | 5 | describe('LandingSection', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/LoginModal/LoginModal.stories.js: -------------------------------------------------------------------------------- 1 | import LoginModal from './LoginModal' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/LoginModal' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/LoginModal/LoginModal.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import LoginModal from './LoginModal' 4 | 5 | describe('LoginModal', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/LogoType/LogoType.js: -------------------------------------------------------------------------------- 1 | import Tooltip from '@material-ui/core/Tooltip' 2 | import { Link, routes } from '@redwoodjs/router' 3 | import Svg from 'src/components/Svg' 4 | 5 | export default function LogoType({ className="", wrappedInLink=false }) { 6 | return ( 7 |
    8 |
  • 9 | { (wrappedInLink 10 | ? 11 |
    12 | 13 |
    14 | 15 | :
    16 |
    17 | 18 |
    19 |
    20 | )} 21 |
  • 22 |
  • 23 | 24 |
    25 | {/* Because of how specific these styles are to this heading/logo and it doesn't need to be replicated else where as well as it's very precise with the placement of "pre-alpha" I think it's appropriate. */} 26 |

    30 | CadHub 31 |

    32 |
    36 | pre-alpha 37 |
    38 |
    39 |
    40 |
  • 41 |
42 | ) 43 | } -------------------------------------------------------------------------------- /app/web/src/components/OutBound/OutBound.js: -------------------------------------------------------------------------------- 1 | import ReactGA from 'react-ga' 2 | 3 | const OutBound = ({ className, children, to }) => { 4 | return ( 5 | { 10 | ReactGA.event({ 11 | category: 'outbound', 12 | action: to, 13 | }) 14 | return true 15 | }} 16 | rel="noreferrer" 17 | > 18 | {children} 19 | 20 | ) 21 | } 22 | 23 | export default OutBound 24 | -------------------------------------------------------------------------------- /app/web/src/components/OutBound/OutBound.stories.js: -------------------------------------------------------------------------------- 1 | import OutBound from './OutBound' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/OutBound' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/OutBound/OutBound.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import OutBound from './OutBound' 4 | 5 | describe('OutBound', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/PanelToolbar/PanelToolbar.tsx: -------------------------------------------------------------------------------- 1 | import { MouseEventHandler, useContext } from 'react' 2 | import { MosaicWindowContext } from 'react-mosaic-component' 3 | import Svg from 'src/components/Svg/Svg' 4 | import StaticImageMessage from 'src/components/StaticImageMessage/StaticImageMessage' 5 | 6 | const PanelToolbar = ({ 7 | panelName, 8 | showTopGradient, 9 | onClick, 10 | }: { 11 | panelName: 'Viewer' | 'Console' 12 | showTopGradient?: boolean 13 | onClick?: MouseEventHandler 14 | }) => { 15 | const { mosaicWindowActions } = useContext(MosaicWindowContext) 16 | return ( 17 | <> 18 | {showTopGradient && ( 19 |
20 | )} 21 |
22 | {panelName === 'Viewer' && } 23 | 34 | {mosaicWindowActions.connectDragSource( 35 |
36 | 37 |
38 | )} 39 |
40 | 41 | ) 42 | } 43 | 44 | export default PanelToolbar 45 | -------------------------------------------------------------------------------- /app/web/src/components/ProfileSlashLogin/ProfileSlashLogin.stories.tsx: -------------------------------------------------------------------------------- 1 | import ProfileSlashLogin from './ProfileSlashLogin' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/ProfileSlashLogin' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/ProfileSlashLogin/ProfileSlashLogin.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import ProfileSlashLogin from './ProfileSlashLogin' 4 | 5 | describe('ProfileSlashLogin', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/ProfileTextInput/ProfileTextInput.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react' 2 | 3 | import InputText from 'src/components/InputText' 4 | 5 | const ProfileTextInput = ({ fields, isEditable, onChange = () => {} }) => { 6 | return ( 7 |
8 |
12 | {Object.entries(fields).map(([property, value]) => ( 13 | 14 | 15 | {property}: 16 | 17 | 18 | 22 | onChange({ ...fields, [property]: target.value }) 23 | } 24 | isEditable={isEditable} 25 | /> 26 | 27 | ))} 28 |
29 |
30 | ) 31 | } 32 | 33 | export default ProfileTextInput 34 | -------------------------------------------------------------------------------- /app/web/src/components/ProfileTextInput/ProfileTextInput.stories.js: -------------------------------------------------------------------------------- 1 | import ProfileTextInput from './ProfileTextInput' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/ProfileTextInput' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/ProfileTextInput/ProfileTextInput.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import ProfileTextInput from './ProfileTextInput' 4 | 5 | describe('ProfileTextInput', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/ProfileViewer/ProfileViewer.tsx: -------------------------------------------------------------------------------- 1 | import IdeViewer from 'src/components/IdeViewer/IdeViewer' 2 | import { use3dViewerResize } from 'src/helpers/hooks/use3dViewerResize' 3 | 4 | const ProfileViewer = () => { 5 | const { viewerDomRef } = use3dViewerResize() 6 | return ( 7 |
8 | 9 |
10 | ) 11 | } 12 | 13 | export default ProfileViewer 14 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectCell/ProjectCell.mock.ts: -------------------------------------------------------------------------------- 1 | // Define your own mock data here: 2 | export const standard = (/* vars, { ctx, req } */) => ({ 3 | project: { 4 | id: 42, 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectCell/ProjectCell.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Loading, Empty, Failure, Success } from './ProjectCell' 2 | import { standard } from './ProjectCell.mock' 3 | 4 | export const loading = () => { 5 | return Loading ? : null 6 | } 7 | 8 | export const empty = () => { 9 | return Empty ? : null 10 | } 11 | 12 | export const failure = () => { 13 | return Failure ? : null 14 | } 15 | 16 | export const success = () => { 17 | return Success ? : null 18 | } 19 | 20 | export default { title: 'Cells/ProjectCell' } 21 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectCell/ProjectCell.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@redwoodjs/testing' 2 | import { Loading, Empty, Failure, Success } from './ProjectCell' 3 | import { standard } from './ProjectCell.mock' 4 | 5 | describe('ProjectCell', () => { 6 | test('Loading renders successfully', () => { 7 | render() 8 | // Use screen.debug() to see output 9 | expect(screen.getByText('Loading...')).toBeInTheDocument() 10 | }) 11 | 12 | test('Empty renders successfully', async () => { 13 | render() 14 | expect(screen.getByText('Empty')).toBeInTheDocument() 15 | }) 16 | 17 | test('Failure renders successfully', async () => { 18 | render() 19 | expect(screen.getByText(/Oh no/i)).toBeInTheDocument() 20 | }) 21 | 22 | test('Success renders successfully', async () => { 23 | render() 24 | expect(screen.getByText(/42/i)).toBeInTheDocument() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectReactionsCell/ProjectReactionsCell.mock.ts: -------------------------------------------------------------------------------- 1 | // Define your own mock data here: 2 | export const standard = (/* vars, { ctx, req } */) => ({ 3 | projectReactions: { 4 | id: 42, 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectReactionsCell/ProjectReactionsCell.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Loading, Empty, Failure, Success } from './ProjectReactionsCell' 2 | import { standard } from './ProjectReactionsCell.mock' 3 | 4 | export const loading = () => { 5 | return Loading ? : null 6 | } 7 | 8 | export const empty = () => { 9 | return Empty ? : null 10 | } 11 | 12 | export const failure = () => { 13 | return Failure ? : null 14 | } 15 | 16 | export const success = () => { 17 | return Success ? : null 18 | } 19 | 20 | export default { title: 'Cells/ProjectReactionsCell' } 21 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectReactionsCell/ProjectReactionsCell.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@redwoodjs/testing' 2 | import { Loading, Empty, Failure, Success } from './ProjectReactionsCell' 3 | import { standard } from './ProjectReactionsCell.mock' 4 | 5 | describe('ProjectReactionsCell', () => { 6 | test('Loading renders successfully', () => { 7 | render() 8 | // Use screen.debug() to see output 9 | expect(screen.getByText('Loading...')).toBeInTheDocument() 10 | }) 11 | 12 | test('Empty renders successfully', async () => { 13 | render() 14 | expect(screen.getByText('Empty')).toBeInTheDocument() 15 | }) 16 | 17 | test('Failure renders successfully', async () => { 18 | render() 19 | expect(screen.getByText(/Oh no/i)).toBeInTheDocument() 20 | }) 21 | 22 | test('Success renders successfully', async () => { 23 | render() 24 | expect(screen.getByText(/42/i)).toBeInTheDocument() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectReactionsCell/ProjectReactionsCell.tsx: -------------------------------------------------------------------------------- 1 | import ProjectReactions from 'src/components/ProjectReactions/ProjectReactions' 2 | 3 | export const QUERY = gql` 4 | query ProjectReactionsQuery($projectId: String!) { 5 | projectReactionsByProjectId(projectId: $projectId) { 6 | id 7 | emote 8 | user { 9 | id 10 | userName 11 | image 12 | } 13 | updatedAt 14 | } 15 | } 16 | ` 17 | 18 | export const Loading = () =>
Loading...
19 | 20 | export const Empty = () => ( 21 |
22 | No reactions to this project yet 😕 23 |
24 | ) 25 | 26 | export const Failure = ({ error }) =>
Error: {error.message}
27 | 28 | export const Success = ({ projectReactionsByProjectId }) => { 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectsCell/ProjectsCell.tsx: -------------------------------------------------------------------------------- 1 | import { Link, routes } from '@redwoodjs/router' 2 | 3 | import Projects from 'src/components/Projects/Projects' 4 | 5 | export const QUERY = gql` 6 | query PROJECTS { 7 | projects { 8 | id 9 | title 10 | cadPackage 11 | mainImage 12 | childForks { 13 | id 14 | } 15 | createdAt 16 | updatedAt 17 | user { 18 | image 19 | userName 20 | } 21 | Reaction { 22 | emote 23 | } 24 | } 25 | } 26 | ` 27 | 28 | export const Loading = () =>
Loading...
29 | 30 | export const Empty = () => { 31 | return
{'No projects yet.'}
32 | } 33 | 34 | export const Success = ({ 35 | projects, 36 | variables: { shouldFilterProjectsWithoutImage, projectLimit }, 37 | }) => { 38 | return ( 39 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectsOfUserCell/ProjectsOfUserCell.mock.ts: -------------------------------------------------------------------------------- 1 | // Define your own mock data here: 2 | export const standard = (/* vars, { ctx, req } */) => ({ 3 | projectsOfUser: { 4 | id: 42, 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectsOfUserCell/ProjectsOfUserCell.stories.jsx: -------------------------------------------------------------------------------- 1 | import { Loading, Empty, Failure, Success } from './ProjectsOfUserCell' 2 | import { standard } from './ProjectsOfUserCell.mock' 3 | 4 | export const loading = () => { 5 | return Loading ? : null 6 | } 7 | 8 | export const empty = () => { 9 | return Empty ? : null 10 | } 11 | 12 | export const failure = () => { 13 | return Failure ? : null 14 | } 15 | 16 | export const success = () => { 17 | return Success ? : null 18 | } 19 | 20 | export default { title: 'Cells/ProjectsOfUserCell' } 21 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectsOfUserCell/ProjectsOfUserCell.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@redwoodjs/testing' 2 | import { Loading, Empty, Success } from './ProjectsOfUserCell' 3 | import { standard } from './ProjectsOfUserCell.mock' 4 | 5 | describe('ProjectsOfUserCell', () => { 6 | test('Loading renders successfully', () => { 7 | render() 8 | // Use screen.debug() to see output 9 | expect(screen.getByText('Loading...')).toBeInTheDocument() 10 | }) 11 | 12 | test('Empty renders successfully', async () => { 13 | render() 14 | expect(screen.getByText('Empty')).toBeInTheDocument() 15 | }) 16 | 17 | test('Success renders successfully', async () => { 18 | render() 19 | expect(screen.getByText(/42/i)).toBeInTheDocument() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /app/web/src/components/ProjectsOfUserCell/ProjectsOfUserCell.tsx: -------------------------------------------------------------------------------- 1 | import type { Projects_Of_User } from 'types/graphql' 2 | import Projects from 'src/components/Projects/Projects' 3 | export const QUERY = gql` 4 | query PROJECTS_OF_USER($userName: String!) { 5 | projects(userName: $userName) { 6 | id 7 | title 8 | mainImage 9 | cadPackage 10 | childForks { 11 | id 12 | } 13 | createdAt 14 | updatedAt 15 | user { 16 | id 17 | image 18 | userName 19 | } 20 | Reaction { 21 | id 22 | emote 23 | } 24 | } 25 | } 26 | ` 27 | 28 | export const Loading = () =>
Loading...
29 | 30 | export const Empty = ({ isMinimal = false }) => { 31 | return ( 32 |

33 | No projects yet. 34 |

35 | ) 36 | } 37 | 38 | export const Success = ({ 39 | projects, 40 | shouldFilterProjectsWithoutImage = false, 41 | projectLimit = 80, 42 | }: { 43 | projects: Projects_Of_User['projects'] 44 | shouldFilterProjectsWithoutImage: boolean 45 | projectLimit: number 46 | }) => { 47 | return ( 48 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /app/web/src/components/RecentProjectsCell/RecentProjectsCell.tsx: -------------------------------------------------------------------------------- 1 | import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' 2 | 3 | import type { Projects_Of_User } from 'types/graphql' 4 | import { Link, routes } from '@redwoodjs/router' 5 | import { QUERY as _QUERY } from 'src/components/ProjectsOfUserCell/ProjectsOfUserCell' 6 | 7 | export const QUERY = _QUERY 8 | 9 | export const Loading = () =>
Loading...
10 | 11 | export const Empty = () =>

None yet!

12 | 13 | export const Failure = ({ error }: CellFailureProps) => ( 14 |
Error: {error.message}
15 | ) 16 | 17 | export const Success = ({ projects }: CellSuccessProps) => { 18 | const filteredProjects = React.useMemo( 19 | () => 20 | projects 21 | .slice(0, 3) 22 | .sort( 23 | (a, b) => 24 | new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() 25 | ), 26 | [projects] 27 | ) 28 | return ( 29 |
30 |
    31 | {filteredProjects.map(({ title, user }, index) => ( 32 | 40 |

    {title.replace(/[-_]/g, ' ')}

    41 | 42 | ))} 43 |
44 |
45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /app/web/src/components/Seo/Seo.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet' 2 | import { useIsBrowser } from '@redwoodjs/prerender/browserUtils' 3 | 4 | const Seo = ({ 5 | title = 'CadHub', 6 | description = 'Edit this part of CadHub', 7 | lang = 'en-US', 8 | socialImageUrl, 9 | }: { 10 | title: string 11 | description: string 12 | lang: string 13 | socialImageUrl?: string 14 | }) => { 15 | const browser = useIsBrowser() 16 | return ( 17 | <> 18 | 25 | {title && {title || 'cadhub'}} 26 | {description && } 27 | 28 | {/* Facebook Meta Tags */} 29 | {browser && } 30 | 31 | {title && } 32 | {description && ( 33 | 34 | )} 35 | {socialImageUrl && ( 36 | 37 | )} 38 | 39 | {/* Twitter Meta Tags */} 40 | 41 | 42 | {browser && } 43 | {title && } 44 | {description && ( 45 | 46 | )} 47 | {socialImageUrl && ( 48 | 49 | )} 50 | 51 | {lang && } 52 | 53 | 54 | ) 55 | } 56 | 57 | export default Seo 58 | -------------------------------------------------------------------------------- /app/web/src/components/StaticImageMessage/StaticImageMessage.tsx: -------------------------------------------------------------------------------- 1 | import OutBound from 'src/components/OutBound/OutBound' 2 | import { useIdeContext } from 'src/helpers/hooks/useIdeContext' 3 | 4 | const StaticImageMessage = () => { 5 | const { state } = useIdeContext() 6 | if ((state.ideType !== 'openscad' && state.ideType !== 'curv') || state.objectData?.type !== 'png') { 7 | return null 8 | } 9 | return state.ideType === 'openscad' ? 10 | 14 | Why reload each camera move? 15 | 16 | :
17 | Alpha Curv integration, no camera support currently. 18 |
19 | } 20 | 21 | export default StaticImageMessage 22 | -------------------------------------------------------------------------------- /app/web/src/components/SubjectAccessRequestCell/SubjectAccessRequestCell.js: -------------------------------------------------------------------------------- 1 | import SubjectAccessRequest from 'src/components/SubjectAccessRequest' 2 | 3 | export const QUERY = gql` 4 | query FIND_SUBJECT_ACCESS_REQUEST_BY_ID($id: String!) { 5 | subjectAccessRequest: subjectAccessRequest(id: $id) { 6 | id 7 | comment 8 | payload 9 | userId 10 | createdAt 11 | updatedAt 12 | } 13 | } 14 | ` 15 | 16 | export const Loading = () =>
Loading...
17 | 18 | export const Empty = () =>
SubjectAccessRequest not found
19 | 20 | export const Success = ({ subjectAccessRequest }) => { 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /app/web/src/components/SubjectAccessRequestsCell/SubjectAccessRequestsCell.js: -------------------------------------------------------------------------------- 1 | import SubjectAccessRequests from 'src/components/SubjectAccessRequests' 2 | 3 | export const QUERY = gql` 4 | query SUBJECT_ACCESS_REQUESTS { 5 | subjectAccessRequests { 6 | id 7 | comment 8 | payload 9 | userId 10 | createdAt 11 | updatedAt 12 | } 13 | } 14 | ` 15 | 16 | export const Loading = () =>
Loading...
17 | 18 | export const Empty = () => { 19 | return
No subjectAccessRequests yet.
20 | } 21 | 22 | export const Success = ({ subjectAccessRequests }) => { 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /app/web/src/components/Toggle/Toggle.tsx: -------------------------------------------------------------------------------- 1 | const Toggle = ({ offLabel = 'off', onLabel = 'on', checked, onChange }) => { 2 | return ( 3 |
4 | {offLabel} 5 |
11 |
21 |
22 | {onLabel} 23 | 29 |
30 | ) 31 | } 32 | 33 | export default Toggle 34 | -------------------------------------------------------------------------------- /app/web/src/components/TopNav/TopNav.tsx: -------------------------------------------------------------------------------- 1 | import { Link, routes } from '@redwoodjs/router' 2 | import Svg from 'src/components/Svg/Svg' 3 | import NavPlusButton from 'src/components/NavPlusButton' 4 | import ProfileSlashLogin from 'src/components/ProfileSlashLogin' 5 | import { ReactNode } from 'react' 6 | interface IdeHeaderProps { 7 | children?: ReactNode 8 | } 9 | 10 | const TopNav = ({ children }: IdeHeaderProps) => { 11 | return ( 12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | {children} 21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | export default TopNav 32 | -------------------------------------------------------------------------------- /app/web/src/components/UserProfile/UserProfile.stories.js: -------------------------------------------------------------------------------- 1 | import UserProfile from './UserProfile' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Components/UserProfile' } 8 | -------------------------------------------------------------------------------- /app/web/src/components/UserProfile/UserProfile.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import UserProfile from './UserProfile' 4 | 5 | describe('UserProfile', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/components/UsersCell/UsersCell.js: -------------------------------------------------------------------------------- 1 | import { Link, routes } from '@redwoodjs/router' 2 | 3 | import Users from 'src/components/Users' 4 | 5 | export const QUERY = gql` 6 | query USERS { 7 | users { 8 | id 9 | userName 10 | email 11 | createdAt 12 | updatedAt 13 | image 14 | bio 15 | } 16 | } 17 | ` 18 | 19 | export const Loading = () =>
Loading...
20 | 21 | export const Empty = () => { 22 | return ( 23 |
24 | {'No users yet. '} 25 | 26 | {'Create one?'} 27 | 28 |
29 | ) 30 | } 31 | 32 | export const Success = ({ users }) => { 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /app/web/src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/web/src/font-imports.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css2?family=Ropa+Sans&display=swap'; 2 | @import 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,500;1,700&display=swap'; 3 | @import 'https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300;400;500;600;700&display=swap'; 4 | @import 'https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap'; 5 | 6 | /* 7 | Putting fonts in a separate css files is a robust way of making sure imports work with post-css 8 | https://tailwindcss.com/docs/using-with-preprocessors#build-time-imports 9 | */ 10 | -------------------------------------------------------------------------------- /app/web/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | // While the raw-loader Webpack plugin actually makes these imports work, this 2 | // eliminates noisy TypeScript errors by registering these file endings as types. 3 | // Learned this method of registering modules from https://stackoverflow.com/a/57444766 4 | declare module '*.md' 5 | declare module '*.scad' 6 | declare module '*.py' 7 | declare module '*.jscad.js' 8 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/cadQuery/cadQueryController.ts: -------------------------------------------------------------------------------- 1 | import { 2 | lambdaBaseURL, 3 | stlToGeometry, 4 | createHealthyResponse, 5 | createUnhealthyResponse, 6 | timeoutErrorMessage, 7 | RenderArgs, 8 | DefaultKernelExport, 9 | splitGziped, 10 | } from '../common' 11 | import { CadQueryToCadhubParams } from './cadQueryParams' 12 | 13 | export const render: DefaultKernelExport['render'] = async ({ 14 | code, 15 | settings: { quality = 'low', parameters }, 16 | }: RenderArgs) => { 17 | const body = JSON.stringify({ 18 | settings: { 19 | deflection: quality === 'low' ? 0.35 : 0.11, 20 | parameters, 21 | }, 22 | file: code, 23 | }) 24 | try { 25 | const response = await fetch(lambdaBaseURL + '/cadquery/stl', { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | body, 31 | }) 32 | if (response.status === 400) { 33 | const { error } = await response.json() 34 | return { 35 | status: 'error', 36 | message: { 37 | type: 'error', 38 | message: error, 39 | time: new Date(), 40 | }, 41 | } 42 | } 43 | if (response.status === 502) { 44 | return createUnhealthyResponse(new Date(), timeoutErrorMessage) 45 | } 46 | const blob = await response.blob() 47 | const text = await new Response(blob).text() 48 | const { consoleMessage, customizerParams } = splitGziped(text) 49 | return createHealthyResponse({ 50 | type: 'geometry', 51 | data: await stlToGeometry(window.URL.createObjectURL(blob)), 52 | consoleMessage, 53 | date: new Date(), 54 | customizerParams: CadQueryToCadhubParams(customizerParams), 55 | }) 56 | } catch (e) { 57 | return createUnhealthyResponse(new Date()) 58 | } 59 | } 60 | 61 | const cadQuery: DefaultKernelExport = { 62 | render, 63 | } 64 | 65 | export default cadQuery 66 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/cadQuery/cadQueryParams.ts: -------------------------------------------------------------------------------- 1 | import { CadhubParams } from 'src/components/Customizer/customizerConverter' 2 | 3 | interface CadQueryParamsBase { 4 | name: string 5 | initial: number | string | boolean 6 | type?: 'number' | 'string' | 'boolean' 7 | } 8 | 9 | interface CadQueryNumberParam extends CadQueryParamsBase { 10 | type: 'number' 11 | initial: number 12 | } 13 | 14 | interface CadQueryStringParam extends CadQueryParamsBase { 15 | type: 'string' 16 | initial: string 17 | } 18 | 19 | interface CadQueryBooleanParam extends CadQueryParamsBase { 20 | type: 'boolean' 21 | initial: boolean 22 | } 23 | 24 | export type CadQueryStringParams = 25 | | CadQueryNumberParam 26 | | CadQueryStringParam 27 | | CadQueryBooleanParam 28 | 29 | export function CadQueryToCadhubParams( 30 | input: CadQueryStringParams[] 31 | ): CadhubParams[] { 32 | return input 33 | .map((param): CadhubParams => { 34 | const common: { caption: string; name: string } = { 35 | caption: '', 36 | name: param.name, 37 | } 38 | switch (param.type) { 39 | case 'number': 40 | return { 41 | type: 'number', 42 | input: 'default-number', 43 | ...common, 44 | initial: param.initial || 0, 45 | } 46 | case 'string': 47 | return { 48 | type: 'string', 49 | input: 'default-string', 50 | ...common, 51 | initial: param.initial || '', 52 | } 53 | case 'boolean': 54 | return { 55 | type: 'boolean', 56 | input: 'default-boolean', 57 | ...common, 58 | initial: param.initial || false, 59 | } 60 | } 61 | }) 62 | .filter((a) => a) 63 | } 64 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/cadQuery/initialCode.py: -------------------------------------------------------------------------------- 1 | # demo shaft coupler 2 | 3 | # ^ first comment is used for download title (i.e. "demo-shaft-coupler.stl") 4 | 5 | # CadQuery docs: https://cadquery.readthedocs.io/ 6 | 7 | import cadquery as cq 8 | from cadquery import exporters 9 | 10 | diam = 5.0 11 | 12 | result = (cq.Workplane().circle(diam).extrude(20.0) 13 | .faces(">Z").workplane(invert=True).circle(1.05).cutBlind(8.0) 14 | .faces("> colour C 8 | >> twist (Twists*90*deg/N) 9 | >> rotate {angle: 90*deg, axis: Y_axis} 10 | >> bend{} 11 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/curv/userGuide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Curv 3 | Written with: [Domain-Specific Language](https://martinfowler.com/dsl.html) 4 | Kernal type: Signed distance functions 5 | Maintained by: [Doug Moen and contributors](https://github.com/curv/curv/graphs/contributors) 6 | Documentation: [curv3d.org](https://curv3d.org) 7 | --- 8 | 9 | Curv is a programming language for creating art using mathematics. It's a 2D and 3D geometric modelling tool that supports full colour, animation and 3D printing. 10 | 11 | ### [Examples](https://github.com/curv3d/curv/tree/master/examples) 12 | 13 | - [Flog spiral](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Flog_spiral.curv) 14 | - [Shreks donut](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Fshreks_donut.curv) 15 | - [Wood grain](https://619b5e6c6689420008eedfe5--cadhubxyz.netlify.app/draft/curv#fetch_text_v1=https%3A%2F%2Fraw.githubusercontent.com%2Fcurv3d%2Fcurv%2Fmaster%2Fexamples%2Ffinial.curv) 16 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/demoController.ts: -------------------------------------------------------------------------------- 1 | // not an actual and there fore not imported into index.ts in this folder, 2 | // this is boiler plate code for adding new integrations. 3 | 4 | import { RenderArgs, DefaultKernelExport } from './common' 5 | 6 | export const render: DefaultKernelExport['render'] = async ({ 7 | code, // eslint-disable-line @typescript-eslint/no-unused-vars 8 | settings, // eslint-disable-line @typescript-eslint/no-unused-vars 9 | }: RenderArgs) => { 10 | // do your magic 11 | return { 12 | status: 'healthy', 13 | message: { 14 | type: 'message', 15 | message: 'demo', 16 | time: new Date(), 17 | }, 18 | objectData: { 19 | data: 'any', 20 | type: 'geometry', 21 | }, 22 | } 23 | } 24 | 25 | const demoController: DefaultKernelExport = { 26 | render, 27 | } 28 | 29 | export default demoController 30 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/index.ts: -------------------------------------------------------------------------------- 1 | import { DefaultKernelExport } from './common' 2 | import type { CadPackageType } from 'src/components/CadPackage/CadPackage' 3 | 4 | import openscad from './openScad/openScadController' 5 | import openScadGuide from 'src/helpers/cadPackages/openScad/userGuide.md' 6 | import openScadInitialCode from 'src/helpers/cadPackages/openScad/initialCode.scad' 7 | 8 | import cadquery from './cadQuery/cadQueryController' 9 | import cadQueryGuide from 'src/helpers/cadPackages/cadQuery/userGuide.md' 10 | import cadQueryInitialCode from 'src/helpers/cadPackages/cadQuery/initialCode.py' 11 | 12 | import jscad from './jsCad/jsCadController' 13 | import jsCadGuide from 'src/helpers/cadPackages/jsCad/userGuide.md' 14 | import jsCadInitialCode from 'src/helpers/cadPackages/jsCad/initialCode.jscad.js' 15 | 16 | import curv from './curv/curvController' 17 | import curvGuide from 'src/helpers/cadPackages/curv/userGuide.md' 18 | import curvInitialCode from 'src/helpers/cadPackages/curv/initialCode.curv' 19 | 20 | export const cadPackages: { [key in CadPackageType]: DefaultKernelExport } = { 21 | openscad, 22 | cadquery, 23 | jscad, 24 | curv, 25 | } 26 | 27 | export const initGuideMap: { [key in CadPackageType]: string } = { 28 | openscad: openScadGuide, 29 | cadquery: cadQueryGuide, 30 | jscad: jsCadGuide, 31 | curv: curvGuide, 32 | INIT: '', 33 | } 34 | 35 | export const initCodeMap: { [key in CadPackageType]: string } = { 36 | openscad: openScadInitialCode, 37 | cadquery: cadQueryInitialCode, 38 | jscad: jsCadInitialCode, 39 | curv: curvInitialCode, 40 | INIT: '', 41 | } 42 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/jsCad/initialCode.jscad.js: -------------------------------------------------------------------------------- 1 | const jscad = require('@jscad/modeling') 2 | // https://openjscad.xyz/docs/module-modeling_primitives.html 3 | const { cuboid, cylinder } = jscad.primitives 4 | 5 | const { rotate, translate } = jscad.transforms 6 | const { degToRad } = jscad.utils // because jscad uses radians for rotations 7 | // https://openjscad.xyz/docs/module-modeling_booleans.html 8 | const { subtract } = jscad.booleans 9 | 10 | function main({ 11 | //@jscad-params 12 | // Box example 13 | width = 40, // Width 14 | length = 20, // Length 15 | height = 10, // Height 16 | hole = 3, // Hole for cables diameter (0=no hole) 17 | wall = 1, // wall {min:0.5, step:0.5} 18 | flip = 0, // print orientation {type: 'choice', values: [0, 90, 180]} 19 | }) { 20 | let wallOffset = wall * 2 21 | let model = subtract( 22 | cuboid({ size: [width, length, height] }), 23 | translate( 24 | [0, 0, wall], 25 | cuboid({ size: [width - wallOffset, length - wallOffset, height + wall] }) 26 | ) 27 | ) 28 | if (hole) { 29 | model = subtract( 30 | model, 31 | translate( 32 | [width / 2 - wall / 2], 33 | rotate( 34 | [0, degToRad(90), 0], 35 | cylinder({ radius: hole / 2, height: wall }) 36 | ) 37 | ) 38 | ) 39 | } 40 | return rotate([degToRad(flip), 0, degToRad(90)], model) 41 | } 42 | 43 | module.exports = { main } // eslint-disable-line 44 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/jsCad/userGuide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JSCAD 3 | Written with: JavaScript 4 | Kernal type: Mesh CSG 5 | Maintained by: [Z3 Development + 39 contributors](https://www.github.com/jscad) 6 | Documentation: [openjscad.xyz/docs](https://openjscad.xyz/docs/) 7 | --- 8 | JSCAD is an open source set of modular, browser and command line tools for creating parametric 2D and 3D designs with Javascript code. It provides a quick, precise and reproducible method for generating 3D models, and is especially useful for 3D printing applications. 9 | -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/openScad/initialCode.scad: -------------------------------------------------------------------------------- 1 | // involute donut 2 | 3 | // ^ first comment is used for download title (i.e "involute-donut.stl") 4 | 5 | // Follow the OpenSCAD tutorial: https://learn.cadhub.xyz/docs/ 6 | 7 | radius=3; 8 | color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30); 9 | color(c="hotpink")rotate_extrude()translate([20,0])offset(radius)offset(-radius)difference(){ 10 | circle(d=34); 11 | translate([-200,-500])square([500,500]); 12 | } -------------------------------------------------------------------------------- /app/web/src/helpers/cadPackages/openScad/userGuide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: OpenSCAD 3 | Written with: [Domain-Specific Language](https://martinfowler.com/dsl.html) 4 | Kernal type: Mesh CSG 5 | Maintained by: [Marius Kintel and contributors](https://github.com/openscad/openscad/graphs/contributors) 6 | Documentation: [openscad.org](https://openscad.org/documentation.html) 7 | --- 8 | OpenSCAD is a solid 3D modeler that enables the creation of parametric models using its scripting language. Models are created by utilizing a technique called constructive solid geometry. With this technique, simple objects can be transformed and combined in order to create almost any complex model. 9 | 10 | A good place to start is the [docs](https://openscad.org/documentation.html), and the [cheatsheet](https://openscad.org/cheatsheet/) is an excellent reference. 11 | -------------------------------------------------------------------------------- /app/web/src/helpers/canvasToBlob.ts: -------------------------------------------------------------------------------- 1 | export const canvasToBlob = async ( 2 | threeInstance, 3 | { width, height }: { width: number; height: number } 4 | ): Promise => { 5 | const updateCanvasSize = ({ 6 | width, 7 | height, 8 | }: { 9 | width: number 10 | height: number 11 | }) => { 12 | threeInstance.camera.aspect = width / height 13 | threeInstance.camera.updateProjectionMatrix() 14 | threeInstance.gl.setSize(width, height) 15 | threeInstance.gl.render( 16 | threeInstance.scene, 17 | threeInstance.camera, 18 | null, 19 | false 20 | ) 21 | } 22 | const oldSize = threeInstance.size 23 | updateCanvasSize({ width, height }) 24 | const imgBlobPromise: Promise = new Promise((resolve) => { 25 | threeInstance.gl.domElement.toBlob( 26 | (blob) => { 27 | resolve(blob) 28 | }, 29 | 'image/png', 30 | 0.75 31 | ) 32 | }) 33 | updateCanvasSize(oldSize) 34 | return imgBlobPromise 35 | } 36 | 37 | export const blobTo64 = (blob: Blob): Promise => { 38 | return new Promise((resolve, reject) => { 39 | const reader = new FileReader() 40 | reader.onloadend = () => { 41 | if (typeof reader.result === 'string') { 42 | resolve(reader.result) 43 | } 44 | } 45 | reader.onerror = reject 46 | reader.readAsDataURL(blob) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /app/web/src/helpers/clipboard.tsx: -------------------------------------------------------------------------------- 1 | import { toast } from '@redwoodjs/web/toast' 2 | 3 | function fallbackCopyTextToClipboard(text: string) { 4 | const textArea = document.createElement('textarea') 5 | textArea.value = text 6 | 7 | // Avoid scrolling to bottom 8 | textArea.style.top = '0' 9 | textArea.style.left = '0' 10 | textArea.style.position = 'fixed' 11 | 12 | document.body.appendChild(textArea) 13 | textArea.focus() 14 | textArea.select() 15 | 16 | try { 17 | const successful = document.execCommand('copy') 18 | const msg = successful ? 'successful' : 'unsuccessful' 19 | console.log('Fallback: Copying text command was ' + msg) 20 | } catch (err) { 21 | console.error('Fallback: Oops, unable to copy', err) 22 | } 23 | 24 | document.body.removeChild(textArea) 25 | } 26 | 27 | const clipboardSuccessToast = () => 28 | toast.success(() => ( 29 |
30 |

link added to clipboard.

31 |
32 | )) 33 | 34 | const makeClipboardCopier = (success: Function) => (text: string) => { 35 | if (!navigator.clipboard) { 36 | fallbackCopyTextToClipboard(text) 37 | success(text) 38 | return 39 | } 40 | navigator.clipboard.writeText(text).then( 41 | function () { 42 | console.log('Async: Copying to clipboard was successful!') 43 | success(text) 44 | }, 45 | function (err) { 46 | console.error('Async: Could not copy text: ', err) 47 | } 48 | ) 49 | } 50 | 51 | export const copyTextToClipboard = makeClipboardCopier(clipboardSuccessToast) 52 | -------------------------------------------------------------------------------- /app/web/src/helpers/compress.ts: -------------------------------------------------------------------------------- 1 | import { inflate, deflate } from 'pako' 2 | 3 | /* 4 | some magic to get scripts to efficiently encoded into the URL. 5 | We're using pako to compress the script, but this outputs to a 8bit array. Stringifying this array adds a lot of overhead, because "125" has three characters in it 6 | Instead we're using the character codes to turn these a bit numbers into single characters 7 | base64 is used as well because not all of the characters are allowed in a url (and b64 is better than encodeURIComponent) 8 | */ 9 | 10 | export const encode = (string: string): string => 11 | btoa(String.fromCharCode.apply(null, deflate(string))) 12 | 13 | export const decode = (string: string): string => 14 | inflate( 15 | new Uint8Array( 16 | atob(string) 17 | .split('') 18 | .map((character) => character.charCodeAt(0)) 19 | ), 20 | { to: 'string' } 21 | ) 22 | -------------------------------------------------------------------------------- /app/web/src/helpers/emote.js: -------------------------------------------------------------------------------- 1 | export const countEmotes = (reactions = []) => { 2 | // would be good to do this sever side 3 | // counting unique emojis, and limiting to the 5 largest 4 | const emoteCounts = {} 5 | reactions.forEach(({ emote }) => { 6 | emoteCounts[emote] = emoteCounts[emote] ? emoteCounts[emote] + 1 : 1 7 | }) 8 | // TODO the sort is causing the emotes to jump around after the user clicks one, not ideal 9 | return Object.entries(emoteCounts) 10 | .map(([emoji, count]) => ({ emoji, count })) 11 | .sort((a, b) => a.count - b.count) 12 | .slice(-5) 13 | } 14 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/use3dViewerResize.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react' 2 | import { useIdeContext } from './useIdeContext' 3 | import { requestRender } from './useIdeState' 4 | 5 | export const use3dViewerResize = () => { 6 | const viewerDomRef = useRef(null) 7 | const debounceTimeoutId = useRef() 8 | const { thunkDispatch } = useIdeContext() 9 | 10 | useEffect(handleViewerSizeUpdate, [viewerDomRef]) 11 | 12 | function handleViewerSizeUpdate() { 13 | if (viewerDomRef !== null && viewerDomRef.current) { 14 | const { width, height } = viewerDomRef.current.getBoundingClientRect() 15 | thunkDispatch({ 16 | type: 'updateViewerSize', 17 | payload: { viewerSize: { width, height } }, 18 | }) 19 | thunkDispatch((dispatch, getState) => { 20 | const state = getState() 21 | if (state.objectData?.type === 'png') { 22 | dispatch({ type: 'setLoading' }) 23 | requestRender({ 24 | state, 25 | dispatch, 26 | viewerSize: { width, height }, 27 | viewAll: state.objectData?.type === 'INIT', 28 | }) 29 | } 30 | }) 31 | } 32 | } 33 | const debouncedViewerSizeUpdate = () => { 34 | clearTimeout(debounceTimeoutId.current) 35 | debounceTimeoutId.current = setTimeout(() => { 36 | handleViewerSizeUpdate() 37 | }, 1000) as unknown as number 38 | } 39 | 40 | useEffect(() => { 41 | window.addEventListener('resize', debouncedViewerSizeUpdate) 42 | return () => { 43 | window.removeEventListener('resize', debouncedViewerSizeUpdate) 44 | } 45 | }, []) 46 | return { 47 | viewerDomRef, 48 | handleViewerSizeUpdate, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useEdgeSplit.ts: -------------------------------------------------------------------------------- 1 | // This code has been copied from https://github.com/pmndrs/drei/blob/master/src/core/useEdgeSplit.tsx 2 | // but modified to allow for updating geometry with `dependantGeometry` 3 | 4 | import * as React from 'react' 5 | import * as THREE from 'three' 6 | import { EdgeSplitModifier } from 'three-stdlib' 7 | 8 | export function useEdgeSplit( 9 | cutOffAngle: number, 10 | tryKeepNormals = true, 11 | dependantGeometry?: any 12 | ) { 13 | const ref = React.useRef() 14 | const original = React.useRef() 15 | const modifier = React.useRef() 16 | 17 | React.useEffect(() => { 18 | if (!original.current && ref.current) { 19 | original.current = ref.current.geometry?.clone() 20 | modifier.current = new EdgeSplitModifier() 21 | } 22 | }, []) 23 | 24 | React.useEffect(() => { 25 | original.current = dependantGeometry?.clone?.() 26 | modifier.current = new EdgeSplitModifier() 27 | }, [dependantGeometry]) 28 | 29 | React.useEffect(() => { 30 | if (original.current && ref.current && modifier.current) { 31 | const modifiedGeometry = modifier.current.modify( 32 | original.current, 33 | cutOffAngle, 34 | tryKeepNormals 35 | ) 36 | modifiedGeometry.computeVertexNormals() 37 | 38 | ref.current.geometry = modifiedGeometry 39 | } 40 | }, [cutOffAngle, tryKeepNormals, dependantGeometry]) 41 | 42 | return ref 43 | } 44 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useIdeContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react' 2 | import { State, initialState } from 'src/helpers/hooks/useIdeState' 3 | import type { Project } from 'src/components/IdeProjectCell/IdeProjectCell' 4 | 5 | interface IdeContextType { 6 | state: State 7 | thunkDispatch: (actionOrThunk: any) => any 8 | project: null | Project 9 | } 10 | 11 | export const IdeContext = createContext({ 12 | state: initialState, 13 | thunkDispatch: () => {}, 14 | project: null, 15 | }) 16 | 17 | export function useIdeContext() { 18 | return useContext(IdeContext) 19 | } 20 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useKeyPress.js: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react' 2 | 3 | const useKeyPress = (fn) => { 4 | const cb = useRef(fn) 5 | 6 | useEffect(() => { 7 | cb.current = fn 8 | }, [fn]) 9 | 10 | useEffect(() => { 11 | const onUnload = cb.current 12 | 13 | window.addEventListener('keydown', onUnload) 14 | 15 | return () => window.removeEventListener('keydown', onUnload) 16 | }, []) 17 | } 18 | 19 | export default useKeyPress 20 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useMarkdownMetaData.ts: -------------------------------------------------------------------------------- 1 | // Extracts YAML frontmatter from Markdown files 2 | // Gotten from this helpful comment on a react-markdown GitHub Issue: https://github.com/remarkjs/react-markdown/issues/164#issuecomment-890497653 3 | interface MetaData { 4 | [key: string]: string 5 | } 6 | type MarkdownMetaDataReturn = [RegExpExecArray, MetaData] 7 | 8 | export function useMarkdownMetaData(text: string): MarkdownMetaDataReturn { 9 | return React.useMemo(() => { 10 | const metaData: MetaData = {} 11 | const metaRegExp = RegExp(/^---[\r\n](((?!---).|[\r\n])*)[\r\n]---$/m) 12 | // get metadata 13 | const rawMetaData = metaRegExp.exec(text) 14 | 15 | let keyValues 16 | 17 | if (rawMetaData !== null) { 18 | // rawMeta[1] are the stuff between "---" 19 | keyValues = rawMetaData[1].split('\n') 20 | 21 | // which returns a list of key values: ["key1: value", "key2: value"] 22 | keyValues.forEach((keyValue) => { 23 | // split each keyValue to keys and values 24 | const [, key, value] = keyValue.split(/(.+): (.+)/) 25 | metaData[key] = value.trim() 26 | }) 27 | } 28 | return [rawMetaData, metaData] 29 | }, [text]) 30 | } 31 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useUpdateProject.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@redwoodjs/web' 2 | 3 | const UPDATE_PROJECT_MUTATION_HOOK = gql` 4 | mutation UpdateProjectMutationHook( 5 | $id: String! 6 | $input: UpdateProjectInput! 7 | ) { 8 | updateProject: updateProject(id: $id, input: $input) { 9 | title 10 | user { 11 | userName 12 | } 13 | } 14 | } 15 | ` 16 | 17 | export const useUpdateProject = ({ onCompleted }) => { 18 | const [updateProject, { loading, error }] = useMutation( 19 | UPDATE_PROJECT_MUTATION_HOOK, 20 | { onCompleted } 21 | ) 22 | 23 | return { updateProject, loading, error } 24 | } 25 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useUpdateProjectImages.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@redwoodjs/web' 2 | 3 | const UPDATE_PROJECT_IMAGES_MUTATION_HOOK = gql` 4 | mutation updateProjectImages( 5 | $id: String! 6 | $mainImage64: String 7 | $socialCard64: String 8 | ) { 9 | updateProjectImages( 10 | id: $id 11 | mainImage64: $mainImage64 12 | socialCard64: $socialCard64 13 | ) { 14 | id 15 | mainImage 16 | socialCard { 17 | id 18 | url 19 | } 20 | } 21 | } 22 | ` 23 | 24 | export const useUpdateProjectImages = ({ onCompleted = () => {} }) => { 25 | const [updateProjectImages, { loading, error }] = useMutation( 26 | UPDATE_PROJECT_IMAGES_MUTATION_HOOK, 27 | { onCompleted } 28 | ) 29 | 30 | return { updateProjectImages, loading, error } 31 | } 32 | 33 | export const makeSocialPublicId = ( 34 | userName: string, 35 | projectTitle: string 36 | ): string => `u-${userName}-slash-p-${projectTitle}` 37 | -------------------------------------------------------------------------------- /app/web/src/helpers/hooks/useUser.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@redwoodjs/web' 2 | import { useAuth } from '@redwoodjs/auth' 3 | 4 | const QUERY = gql` 5 | query FIND_USER_BY_ID($id: String!) { 6 | user: user(id: $id) { 7 | id 8 | image 9 | userName 10 | name 11 | } 12 | } 13 | ` 14 | 15 | export default function () { 16 | const { currentUser } = useAuth() 17 | const { data, loading } = useQuery(QUERY, { 18 | skip: !currentUser?.sub, 19 | variables: { id: currentUser?.sub }, 20 | }) 21 | return { user: data?.user, loading } 22 | } 23 | -------------------------------------------------------------------------------- /app/web/src/helpers/subscribe.ts: -------------------------------------------------------------------------------- 1 | export const subscribe = ({ 2 | email, 3 | addMessage, 4 | name, 5 | }: { 6 | email: string 7 | addMessage: Function 8 | name: string 9 | }) => { 10 | // subscribe to mailchimp newsletter 11 | const path = window.location.hostname + window.location.pathname 12 | try { 13 | fetch( 14 | `https://kurthutten.us10.list-manage.com/subscribe/post-json?u=cbd8888e924bdd99d06c14fa5&id=6a765a8b3d&EMAIL=${email}&FNAME=${name}&PATHNAME=${path}&c=__jp0` 15 | ) 16 | } catch (e) { 17 | setTimeout(() => { 18 | addMessage('Problem subscribing to newsletter') 19 | }, 1000) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | CadHub 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | <%= prerenderPlaceholder %> 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /app/web/src/layouts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Irev-Dev/cadhub/4f65c5dde449adf33f4ff312fd0a7e13058dd9d5/app/web/src/layouts/.keep -------------------------------------------------------------------------------- /app/web/src/layouts/MainLayout/MainLayout.css: -------------------------------------------------------------------------------- 1 | .preserve-3d-for-children * { 2 | transform-style: preserve-3d; 3 | } 4 | .preserve-3d-for-children { 5 | transform-style: preserve-3d; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /app/web/src/layouts/MainLayout/MainLayout.stories.js: -------------------------------------------------------------------------------- 1 | import MainLayout from './MainLayout' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Layouts/MainLayout' } 8 | -------------------------------------------------------------------------------- /app/web/src/layouts/MainLayout/MainLayout.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import MainLayout from './MainLayout' 4 | 5 | describe('MainLayout', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/layouts/SubjectAccessRequestsLayout/SubjectAccessRequestsLayout.js: -------------------------------------------------------------------------------- 1 | import { Link, routes } from '@redwoodjs/router' 2 | import { Toaster } from '@redwoodjs/web/toast' 3 | 4 | const SubjectAccessRequestsLayout = (props) => { 5 | return ( 6 |
7 | 8 |
9 |

10 | 11 | SubjectAccessRequests 12 | 13 |

14 |
15 |
{props.children}
16 |
17 | ) 18 | } 19 | 20 | export default SubjectAccessRequestsLayout 21 | -------------------------------------------------------------------------------- /app/web/src/pages/AccountRecoveryPage/AccountRecoveryPage.stories.js: -------------------------------------------------------------------------------- 1 | import AccountRecoveryPage from './AccountRecoveryPage' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Pages/AccountRecoveryPage' } 8 | -------------------------------------------------------------------------------- /app/web/src/pages/AccountRecoveryPage/AccountRecoveryPage.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import AccountRecoveryPage from './AccountRecoveryPage' 4 | 5 | describe('AccountRecoveryPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/AdminProjectsPage/AdminProjectsPage.tsx: -------------------------------------------------------------------------------- 1 | import MainLayout from 'src/layouts/MainLayout/MainLayout' 2 | import AdminProjectsCell from 'src/components/AdminProjectsCell' 3 | 4 | const ProjectsPage = () => { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | 12 | export default ProjectsPage 13 | -------------------------------------------------------------------------------- /app/web/src/pages/CodeOfConductPage/CodeOfConductPage.stories.js: -------------------------------------------------------------------------------- 1 | import CodeOfConductPage from './CodeOfConductPage' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Pages/CodeOfConductPage' } 8 | -------------------------------------------------------------------------------- /app/web/src/pages/CodeOfConductPage/CodeOfConductPage.test.js: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import CodeOfConductPage from './CodeOfConductPage' 4 | 5 | describe('CodeOfConductPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/DevIdePage/DevIdePage.tsx: -------------------------------------------------------------------------------- 1 | import Seo from 'src/components/Seo/Seo' 2 | import IdeWrapper from 'src/components/IdeWrapper/IdeWrapper' 3 | import { Toaster } from '@redwoodjs/web/toast' 4 | import { useIdeState } from 'src/helpers/hooks/useIdeState' 5 | import type { Project } from 'src/components/IdeProjectCell/IdeProjectCell' 6 | import { IdeContext } from 'src/helpers/hooks/useIdeContext' 7 | import type { CadPackageType } from 'src/components/CadPackage/CadPackage' 8 | 9 | interface Props { 10 | cadPackage: string 11 | project?: Project 12 | } 13 | 14 | const DevIdePage = ({ cadPackage, project }: Props) => { 15 | const [state, thunkDispatch] = useIdeState() 16 | return ( 17 |
18 | 23 | 24 | 25 | 28 | 29 |
30 | ) 31 | } 32 | 33 | export default DevIdePage 34 | -------------------------------------------------------------------------------- /app/web/src/pages/DraftProjectPage/DraftProjectPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import DraftProjectPage from './DraftProjectPage' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Pages/DraftProjectPage' } 8 | -------------------------------------------------------------------------------- /app/web/src/pages/DraftProjectPage/DraftProjectPage.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import DraftProjectPage from './DraftProjectPage' 4 | 5 | describe('DraftProjectPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/DraftProjectPage/DraftProjectPage.tsx: -------------------------------------------------------------------------------- 1 | import DevIdePage from 'src/pages/DevIdePage/DevIdePage' 2 | 3 | const DraftProjectPage = ({ cadPackage }: { cadPackage: string }) => { 4 | return 5 | } 6 | 7 | export default DraftProjectPage 8 | -------------------------------------------------------------------------------- /app/web/src/pages/EditProjectPage/EditProjectPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import EditProjectPage from './EditProjectPage' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Pages/EditProjectPage' } 8 | -------------------------------------------------------------------------------- /app/web/src/pages/EditProjectPage/EditProjectPage.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import EditProjectPage from './EditProjectPage' 4 | 5 | describe('EditProjectPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/EditProjectPage/EditProjectPage.tsx: -------------------------------------------------------------------------------- 1 | import { useAuth } from '@redwoodjs/auth' 2 | 3 | import MainLayout from 'src/layouts/MainLayout' 4 | import ProjectCell from 'src/components/ProjectCell' 5 | import Seo from 'src/components/Seo/Seo' 6 | 7 | const EditProjectPage = ({ userName, projectTitle }) => { 8 | const { currentUser } = useAuth() 9 | return ( 10 | 11 | 12 | 13 | 19 | 20 | ) 21 | } 22 | 23 | export default EditProjectPage 24 | -------------------------------------------------------------------------------- /app/web/src/pages/EditSubjectAccessRequestPage/EditSubjectAccessRequestPage.js: -------------------------------------------------------------------------------- 1 | import SubjectAccessRequestsLayout from 'src/layouts/SubjectAccessRequestsLayout' 2 | import EditSubjectAccessRequestCell from 'src/components/EditSubjectAccessRequestCell' 3 | 4 | const EditSubjectAccessRequestPage = ({ id }) => { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | 12 | export default EditSubjectAccessRequestPage 13 | -------------------------------------------------------------------------------- /app/web/src/pages/EditUserPage/EditUserPage.js: -------------------------------------------------------------------------------- 1 | import EditUserCell from 'src/components/EditUserCell' 2 | import Seo from 'src/components/Seo/Seo' 3 | 4 | const UserPage = ({ userName }) => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default UserPage 15 | -------------------------------------------------------------------------------- /app/web/src/pages/EmbedProjectPage/EmbedProjectPage.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import EmbedProjectPage from './EmbedProjectPage' 4 | 5 | describe('EmbedProjectPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/EmbedProjectPage/EmbedProjectPage.tsx: -------------------------------------------------------------------------------- 1 | import EmbedProjectCell from 'src/components/EmbedProjectCell' 2 | 3 | const EmbedProjectPage = ({ userName, projectTitle }) => { 4 | return ( 5 | <> 6 | 7 | 8 | ) 9 | } 10 | 11 | export default EmbedProjectPage 12 | -------------------------------------------------------------------------------- /app/web/src/pages/HomePage/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import MainLayout from 'src/layouts/MainLayout' 2 | import Seo from 'src/components/Seo/Seo' 3 | import { Hero } from 'src/components/Hero/Hero' 4 | 5 | const HomePage = () => { 6 | return ( 7 | 8 | 14 | 15 | 16 | ) 17 | } 18 | 19 | export default HomePage 20 | -------------------------------------------------------------------------------- /app/web/src/pages/IdeProjectPage/IdeProjectPage.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import IdeProjectPage from './IdeProjectPage' 4 | 5 | describe('IdeProjectPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/IdeProjectPage/IdeProjectPage.tsx: -------------------------------------------------------------------------------- 1 | import IdeProjectCell from 'src/components/IdeProjectCell' 2 | import Seo from 'src/components/Seo/Seo' 3 | import { makeSocialPublicId } from 'src/helpers/hooks/useUpdateProjectImages' 4 | 5 | const IdeProjectPage = ({ userName, projectTitle }) => { 6 | const socialImageUrl = `http://res.cloudinary.com/irevdev/image/upload/c_scale,w_1200/v1/CadHub/${makeSocialPublicId( 7 | userName, 8 | projectTitle 9 | )}` 10 | return ( 11 | <> 12 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default IdeProjectPage 24 | -------------------------------------------------------------------------------- /app/web/src/pages/NewProjectPage/NewProjectPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import NewProjectPage from './NewProjectPage' 2 | 3 | export const generated = () => { 4 | return 5 | } 6 | 7 | export default { title: 'Pages/NewProjectPage' } 8 | -------------------------------------------------------------------------------- /app/web/src/pages/NewProjectPage/NewProjectPage.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@redwoodjs/testing' 2 | 3 | import NewProjectPage from './NewProjectPage' 4 | 5 | describe('NewProjectPage', () => { 6 | it('renders successfully', () => { 7 | expect(() => { 8 | render() 9 | }).not.toThrow() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /app/web/src/pages/NewProjectPage/NewProjectPage.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useAuth } from '@redwoodjs/auth' 3 | import { navigate, routes } from '@redwoodjs/router' 4 | 5 | import MainLayout from 'src/layouts/MainLayout' 6 | import ProjectCell from 'src/components/ProjectCell' 7 | import Seo from 'src/components/Seo/Seo' 8 | 9 | const NewProjectPage = ({ userName }) => { 10 | const { isAuthenticated, currentUser } = useAuth() 11 | useEffect(() => { 12 | !isAuthenticated && navigate(routes.home()) 13 | }, [currentUser, isAuthenticated]) 14 | return ( 15 | 16 | 21 | 22 | 27 | 28 | ) 29 | } 30 | 31 | export default NewProjectPage 32 | -------------------------------------------------------------------------------- /app/web/src/pages/NotFoundPage/NotFoundPage.js: -------------------------------------------------------------------------------- 1 | import MainLayout from 'src/layouts/MainLayout' 2 | import Seo from 'src/components/Seo/Seo' 3 | 4 | export default () => ( 5 | 6 | 7 | 8 |