2
0
Files
bot/apps/builder/src/features/folders/components/FolderContent.tsx
2024-06-04 18:25:46 +02:00

235 lines
6.6 KiB
TypeScript

import { DashboardFolder, WorkspaceRole } from '@typebot.io/prisma'
import {
Flex,
Heading,
HStack,
Portal,
Skeleton,
Stack,
useEventListener,
Wrap,
} from '@chakra-ui/react'
import { useTypebotDnd } from '../TypebotDndProvider'
import React, { useEffect, useState } from 'react'
import { BackButton } from './BackButton'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { useToast } from '@/hooks/useToast'
import { CreateBotButton } from './CreateBotButton'
import { CreateFolderButton } from './CreateFolderButton'
import FolderButton, { ButtonSkeleton } from './FolderButton'
import TypebotButton from './TypebotButton'
import { TypebotCardOverlay } from './TypebotButtonOverlay'
import { useTypebots } from '@/features/dashboard/hooks/useTypebots'
import { TypebotInDashboard } from '@/features/dashboard/types'
import { trpc } from '@/lib/trpc'
import { NodePosition } from '@/features/graph/providers/GraphDndProvider'
type Props = { folder: DashboardFolder | null }
export const FolderContent = ({ folder }: Props) => {
const { workspace, currentRole } = useWorkspace()
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
const {
setDraggedTypebot,
draggedTypebot,
mouseOverFolderId,
setMouseOverFolderId,
} = useTypebotDnd()
const [draggablePosition, setDraggablePosition] = useState({ x: 0, y: 0 })
const [mousePositionInElement, setMousePositionInElement] = useState({
x: 0,
y: 0,
})
const { showToast } = useToast()
const {
data: { folders } = {},
isLoading: isFolderLoading,
refetch: refetchFolders,
} = trpc.folders.listFolders.useQuery(
{
workspaceId: workspace?.id as string,
parentFolderId: folder?.id,
},
{
enabled: !!workspace,
onError: (error) => {
showToast({
description: error.message,
})
},
}
)
const { mutate: createFolder } = trpc.folders.createFolder.useMutation({
onError: (error) => {
showToast({ description: error.message })
},
onSuccess: () => {
refetchFolders()
},
})
const { mutate: updateTypebot } = trpc.typebot.updateTypebot.useMutation({
onError: (error) => {
showToast({ description: error.message })
},
onSuccess: () => {
refetchTypebots()
},
})
const {
typebots,
isLoading: isTypebotLoading,
refetch: refetchTypebots,
} = useTypebots({
workspaceId: workspace?.id,
folderId: folder === null ? 'root' : folder.id,
onError: (error) => {
showToast({
description: error.message,
})
},
})
const moveTypebotToFolder = async (typebotId: string, folderId: string) => {
if (!typebots) return
updateTypebot({
typebotId,
typebot: {
folderId: folderId === 'root' ? null : folderId,
},
})
}
const handleCreateFolder = () => {
if (!folders || !workspace) return
setIsCreatingFolder(true)
createFolder({
workspaceId: workspace.id,
parentFolderId: folder?.id,
})
setIsCreatingFolder(false)
}
const handleMouseUp = async () => {
if (mouseOverFolderId !== undefined && draggedTypebot)
await moveTypebotToFolder(draggedTypebot.id, mouseOverFolderId ?? 'root')
setMouseOverFolderId(undefined)
setDraggedTypebot(undefined)
}
useEventListener('mouseup', handleMouseUp)
const handleTypebotDrag =
(typebot: TypebotInDashboard) =>
({ absolute, relative }: NodePosition) => {
if (draggedTypebot) return
setMousePositionInElement(relative)
setDraggablePosition({
x: absolute.x - relative.x,
y: absolute.y - relative.y,
})
setDraggedTypebot(typebot)
}
const handleMouseMove = (e: MouseEvent) => {
if (!draggedTypebot) return
const { clientX, clientY } = e
setDraggablePosition({
x: clientX - mousePositionInElement.x,
y: clientY - mousePositionInElement.y,
})
}
useEventListener('mousemove', handleMouseMove)
useEffect(() => {
if (!draggablePosition || !draggedTypebot) return
const { innerHeight } = window
const scrollSpeed = 10
const scrollMargin = 50
const clientY = draggablePosition.y + mousePositionInElement.y
const scrollY =
clientY < scrollMargin
? -scrollSpeed
: clientY > innerHeight - scrollMargin
? scrollSpeed
: 0
window.scrollBy(0, scrollY)
const interval = setInterval(() => {
window.scrollBy(0, scrollY)
}, 5)
return () => {
clearInterval(interval)
}
}, [draggablePosition, draggedTypebot, mousePositionInElement])
return (
<Flex w="full" flex="1" justify="center">
<Stack w="1000px" spacing={6} pt="4">
<Skeleton isLoaded={folder?.name !== undefined}>
<Heading as="h1">{folder?.name}</Heading>
</Skeleton>
<Stack>
<HStack>
{folder && <BackButton id={folder.parentFolderId} />}
{currentRole !== WorkspaceRole.GUEST && (
<CreateFolderButton
onClick={handleCreateFolder}
isLoading={isCreatingFolder || isFolderLoading}
/>
)}
</HStack>
<Wrap spacing={4}>
{currentRole !== WorkspaceRole.GUEST && (
<CreateBotButton
folderId={folder?.id}
isLoading={isTypebotLoading}
/>
)}
{isFolderLoading && <ButtonSkeleton />}
{folders &&
folders.map((folder, index) => (
<FolderButton
key={folder.id}
index={index}
folder={folder}
onFolderDeleted={refetchFolders}
onFolderRenamed={() => refetchFolders()}
/>
))}
{isTypebotLoading && <ButtonSkeleton />}
{typebots &&
typebots.map((typebot) => (
<TypebotButton
key={typebot.id}
typebot={typebot}
draggedTypebot={draggedTypebot}
onTypebotUpdated={refetchTypebots}
onDrag={handleTypebotDrag(typebot)}
/>
))}
</Wrap>
</Stack>
</Stack>
{draggedTypebot && (
<Portal>
<TypebotCardOverlay
typebot={draggedTypebot}
onMouseUp={handleMouseUp}
pos="fixed"
top="0"
left="0"
style={{
transform: `translate(${draggablePosition.x}px, ${draggablePosition.y}px) rotate(-2deg)`,
}}
transformOrigin="0 0 0"
/>
</Portal>
)}
</Flex>
)
}