Start project fork feature
Updated schema, project service and UI Still some polish to go. Co-authored-by: Frank Noirot <franknoirot@users.noreply.github.com>
This commit is contained in:
@@ -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;
|
||||||
@@ -53,9 +53,12 @@ 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])
|
||||||
|
|
||||||
Comment Comment[]
|
childForks Project[] @relation("Fork")
|
||||||
Reaction ProjectReaction[]
|
Comment Comment[]
|
||||||
|
Reaction ProjectReaction[]
|
||||||
@@unique([title, userId])
|
@@unique([title, userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user