Add project forking #533

Merged
franknoirot merged 7 commits from frank/add-project-forking into main 2021-09-28 14:51:36 +02:00
5 changed files with 79 additions and 9 deletions
Showing only changes of commit 02463db741 - Show all commits

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "forkedFromId" TEXT;
-- AddForeignKey
ALTER TABLE "Project" ADD FOREIGN KEY ("forkedFromId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -53,7 +53,10 @@ model Project {
deleted Boolean @default(false) deleted Boolean @default(false)
cadPackage CadPackage @default(openscad) cadPackage CadPackage @default(openscad)
socialCard SocialCard? socialCard SocialCard?
forkedFromId String?
forkedFrom Project? @relation("Fork", fields: [forkedFromId], references: [id])
childForks Project[] @relation("Fork")
Comment Comment[] Comment Comment[]
Reaction ProjectReaction[] Reaction ProjectReaction[]
@@unique([title, userId]) @@unique([title, userId])

View File

@@ -14,6 +14,9 @@ export const schema = gql`
socialCard: SocialCard socialCard: SocialCard
Comment: [Comment]! Comment: [Comment]!
Reaction(userId: String): [ProjectReaction]! Reaction(userId: String): [ProjectReaction]!
forkedFromId: String
forkedFrom: Project
childForks: [Project]!
} }
enum CadPackage { enum CadPackage {
@@ -37,6 +40,11 @@ export const schema = gql`
cadPackage: CadPackage! cadPackage: CadPackage!
} }
input ForkProjectInput {
userId: String!
forkedFromId: String
}
input UpdateProjectInput { input UpdateProjectInput {
title: String title: String
description: String description: String
@@ -47,7 +55,7 @@ export const schema = gql`
type Mutation { type Mutation {
createProject(input: CreateProjectInput!): Project! createProject(input: CreateProjectInput!): Project!
forkProject(input: CreateProjectInput!): Project! forkProject(input: ForkProjectInput!): Project!
updateProject(id: String!, input: UpdateProjectInput!): Project! updateProject(id: String!, input: UpdateProjectInput!): Project!
updateProjectImages( updateProjectImages(
id: String! id: String!

View File

@@ -78,13 +78,26 @@ export const createProject = async ({ input }: CreateProjectArgs) => {
} }
export const forkProject = async ({ input }) => { export const forkProject = async ({ input }) => {
// Only difference between create and fork project is that fork project will generate a unique title requireAuth()
// (for the user) if there is a conflict const projectData = await db.project.findUnique({
where: {
id: input.forkedFromId,
},
})
const isUniqueCallback = isUniqueProjectTitle(input.userId) const isUniqueCallback = isUniqueProjectTitle(input.userId)
const title = await generateUniqueString(input.title, isUniqueCallback) let title = projectData.title
// TODO change the description to `forked from userName/projectName ${rest of description}`
title = await generateUniqueString(title, isUniqueCallback)
const { code, description, cadPackage } = projectData
return db.project.create({ return db.project.create({
data: foreignKeyReplacement({ ...input, title }), data: foreignKeyReplacement({
...input,
title,
code,
description,
cadPackage,
}),
}) })
} }

View File

@@ -8,11 +8,24 @@ import ExternalScript from 'src/components/EncodedUrl/ExternalScript'
import Svg from 'src/components/Svg/Svg' import Svg from 'src/components/Svg/Svg'
import NavPlusButton from 'src/components/NavPlusButton' import NavPlusButton from 'src/components/NavPlusButton'
import ProfileSlashLogin from 'src/components/ProfileSlashLogin' import ProfileSlashLogin from 'src/components/ProfileSlashLogin'
import { useMutation } from '@redwoodjs/web'
import Gravatar from 'src/components/Gravatar/Gravatar' import Gravatar from 'src/components/Gravatar/Gravatar'
import EditableProjectTitle from 'src/components/EditableProjecTitle/EditableProjecTitle' import EditableProjectTitle from 'src/components/EditableProjecTitle/EditableProjecTitle'
import CaptureButton from 'src/components/CaptureButton/CaptureButton' import CaptureButton from 'src/components/CaptureButton/CaptureButton'
import { ReactNode } from 'react' import { ReactNode } from 'react'
const FORK_PROJECT_MUTATION = gql`
mutation ForkProjectMutation($input: ForkProjectInput!) {
forkProject(input: $input) {
id
title
description
code
}
}
`
const TopButton = ({ const TopButton = ({
onClick, onClick,
children, children,
@@ -111,7 +124,6 @@ const IdeHeader = ({
) : ( ) : (
children children
)} )}
{/* <TopButton>Fork</TopButton> */}
<div className="h-8 w-8"> <div className="h-8 w-8">
<NavPlusButton /> <NavPlusButton />
</div> </div>
@@ -130,6 +142,26 @@ function DefaultTopButtons({
handleRender, handleRender,
franknoirot commented 2021-09-28 00:23:45 +02:00 (Migrated from github.com)
Review

Navigate to either the IDE or the details views to match wherever you are now.

Navigate to either the IDE or the details views to match wherever you are now.
franknoirot commented 2021-09-28 00:25:37 +02:00 (Migrated from github.com)
Review

TODO regularize these params when the Header component gets regularized.

TODO regularize these params when the Header component gets regularized.
Irev-Dev commented 2021-09-28 12:32:39 +02:00 (Migrated from github.com)
Review

This seems a little brittle to me, as I would hope that the component would be able be able to know what page it's on without having to look at the route. What if someone names their project "idea" than it would match on "user/idea" I had a look at seeing what it would take to pass this information to the component instead and realised that it could be done but it would be adding more mess to the messy situation that is this component so I did a big refactor aimed at this branch.

I've put you as a review, but the diff is not super meaningful as it's really just huge chunks of code moved around 🤷

https://github.com/Irev-Dev/cadhub/pull/534

This seems a little brittle to me, as I would hope that the component would be able be able to know what page it's on without having to look at the route. What if someone names their project "idea" than it would match on "user/idea" I had a look at seeing what it would take to pass this information to the component instead and realised that it could be done but it would be adding more mess to the messy situation that is this component so I did a big refactor aimed at this branch. I've put you as a review, but the diff is not super meaningful as it's really just huge chunks of code moved around 🤷 https://github.com/Irev-Dev/cadhub/pull/534
canEdit, canEdit,
}) { }) {
const { currentUser } = useAuth()
const [createFork] = useMutation(FORK_PROJECT_MUTATION, {
onCompleted: () => {},
})
const handleFork = () => {
const prom = createFork({
variables: {
input: {
userId: currentUser.sub,
forkedFromId: project.id,
},
},
})
// toast.promise(prom, {
// loading: 'Saving Image/s',
// success: <b>Image/s saved!</b>,
// error: <b>Problem saving.</b>,
// })
}
return ( return (
<> <>
{canEdit && !projectTitle && ( {canEdit && !projectTitle && (
@@ -212,6 +244,15 @@ function DefaultTopButtons({
) )
}} }}
</Popover> </Popover>
{currentUser?.id && (
<TopButton
onClick={handleFork}
name="Fork"
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
>
<Svg name="fork-new" className="w-6 h-6 text-ch-blue-400" />
</TopButton>
)}
</> </>
) )
} }