Skip to content

Commit

Permalink
fix drag preview on documents and add scroll on drag
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfcosta committed Dec 28, 2024
1 parent f86d696 commit 082c7ff
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 76 deletions.
77 changes: 1 addition & 76 deletions apps/web/src/components/DocumentsTree.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ascend, sortWith } from 'ramda'
import { useDrag, useDrop, useDragLayer, XYCoord } from 'react-dnd'
import { useDrag, useDrop } from 'react-dnd'
import {
EllipsisHorizontalIcon,
TrashIcon,
Expand All @@ -14,7 +14,6 @@ import IconSelector from './IconSelector'
import { Menu, Transition } from '@headlessui/react'
import ReactDOM from 'react-dom'
import {
CSSProperties,
MouseEventHandler,
useCallback,
useEffect,
Expand All @@ -31,79 +30,6 @@ import {
import { List, Stack } from 'immutable'
import { getEmptyImage } from 'react-dnd-html5-backend'

function getItemStyles(
initialCursorOffset: XYCoord | null,
initialOffset: XYCoord | null,
currentOffset: XYCoord | null
) {
if (!initialOffset || !currentOffset || !initialCursorOffset) {
return {
display: 'none',
}
}

const x = initialCursorOffset?.x + (currentOffset.x - initialOffset.x)
const y = initialCursorOffset?.y + (currentOffset.y - initialOffset.y)
const transform = `translate(${x}px, ${y}px)`

return {
transform,
WebkitTransform: transform,
}
}

const layerStyles: CSSProperties = {
position: 'fixed',
pointerEvents: 'none',
zIndex: 100,
left: 0,
top: 0,
width: '100%',
height: '100%',
}

const DocDragLayer = () => {
const {
item,
isDragging,
initialCursorOffset,
initialFileOffset,
currentFileOffset,
} = useDragLayer((monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialCursorOffset: monitor.getInitialClientOffset(),
initialFileOffset: monitor.getInitialSourceClientOffset(),
currentFileOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
}))

if (!isDragging) {
return null
}

return (
<div style={layerStyles}>
<div
style={getItemStyles(
initialCursorOffset,
initialFileOffset,
currentFileOffset
)}
>
<div className="p-1 bg-ceramic-200 rounded-md max-w-48 text-sm opacity-25 truncate flex gap-x-1 items-center py-2 -translate-x-1/2 -translate-y-1/2 scale-[.85] -rotate-6">
<IconSelector
workspaceId={item.workspaceId}
documentId={item.id}
disabled={true}
/>
{item.title || 'Untitled'}
</div>
</div>
</div>
)
}

function useIsDocExpanded(doc: ApiDocument, startsOpen: boolean) {
const [isExpanded, _setIsExpanded] = useState(
localStorage.getItem(`briefer:document:${doc.id}:expanded`) === '1' ||
Expand Down Expand Up @@ -212,7 +138,6 @@ function DocumentTree(props: Props) {

return (
<>
<DocDragLayer />
<ul role="list" className="space-y-1">
{trees.map((node, i) => {
const isLast = i === trees.size - 1
Expand Down
161 changes: 161 additions & 0 deletions apps/web/src/components/DragLayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { CSSProperties, useEffect } from 'react'
import { useDragLayer, XYCoord } from 'react-dnd'
import IconSelector from './IconSelector'
import { ElementType } from './v2Editor'

function getItemStyles(
initialCursorOffset: XYCoord | null,
initialOffset: XYCoord | null,
currentOffset: XYCoord | null
) {
if (!initialOffset || !currentOffset || !initialCursorOffset) {
return {
display: 'none',
}
}

const x = initialCursorOffset?.x + (currentOffset.x - initialOffset.x)
const y = initialCursorOffset?.y + (currentOffset.y - initialOffset.y)
const transform = `translate(${x}px, ${y}px)`

return {
transform,
WebkitTransform: transform,
}
}

const layerStyles: CSSProperties = {
position: 'fixed',
pointerEvents: 'none',
zIndex: 100,
left: 0,
top: 0,
width: '100%',
height: '100%',
}

const isEditorBlock = (itemType: string | symbol | null) => {
return itemType === ElementType.Block || itemType === ElementType.BlockGroup
}

const DragLayer = () => {
const {
item,
itemType,
isDragging,
initialCursorOffset,
initialFileOffset,
currentFileOffset,
} = useDragLayer((monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialCursorOffset: monitor.getInitialClientOffset(),
initialFileOffset: monitor.getInitialSourceClientOffset(),
currentFileOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
}))

useEffect(() => {
if (!isEditorBlock(itemType)) {
return
}

const editorScrollview = document.getElementById('editor-scrollview')
if (!editorScrollview) {
return
}

let animationFrameId: number | null = null
const scrollThresholdPixels = 120

const scrollSmoothly = (scrollAmount: number) => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}

const step = () => {
editorScrollview.scrollBy({ top: scrollAmount })
animationFrameId = requestAnimationFrame(step)
}

animationFrameId = requestAnimationFrame(step)
}

const handleDragOver = (e: DragEvent) => {
const rect = document.body.getBoundingClientRect()
const cursorY = e.clientY

if (cursorY >= rect.top && cursorY <= rect.bottom) {
const relativeY = cursorY - rect.top
const viewportHeight = rect.height

if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}

if (relativeY > viewportHeight - scrollThresholdPixels) {
const distanceFromBottom = viewportHeight - relativeY
const scrollSpeed = Math.max(
2,
(scrollThresholdPixels - distanceFromBottom) / 6
)
scrollSmoothly(scrollSpeed)
} else if (relativeY < scrollThresholdPixels) {
const scrollSpeed = Math.max(
2,
(scrollThresholdPixels - relativeY) / 6
)
scrollSmoothly(-scrollSpeed)
}
}
}

const handleDragEnd = () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
}

document.body.addEventListener('dragover', handleDragOver)
document.addEventListener('dragend', handleDragEnd)
document.addEventListener('drop', handleDragEnd)

return () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}
document.body.removeEventListener('dragover', handleDragOver)
document.removeEventListener('dragend', handleDragEnd)
document.removeEventListener('drop', handleDragEnd)
}
}, [itemType])

if (!isDragging || isEditorBlock(itemType)) {
return null
}

return (
<div style={layerStyles}>
<div
style={getItemStyles(
initialCursorOffset,
initialFileOffset,
currentFileOffset
)}
>
<div className="p-1 bg-ceramic-200 rounded-md max-w-48 text-sm opacity-25 truncate flex gap-x-1 items-center py-2 -translate-x-1/2 -translate-y-1/2 scale-[.85] -rotate-6">
<IconSelector
workspaceId={item.workspaceId}
documentId={item.id}
disabled={true}
/>
{item.title || 'Untitled'}
</div>
</div>
</div>
)
}

export default DragLayer
3 changes: 3 additions & 0 deletions apps/web/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
} from './ConfigurationsMenuItem'
import { FeaturesDialog } from './SubscriptionBadge'
import { SessionUser, useSignout } from '@/hooks/useAuth'
import DragLayer from './DragLayer'

const syne = Syne({ subsets: ['latin'] })

Expand Down Expand Up @@ -304,6 +305,8 @@ export default function Layout({
<div className={`flex w-full h-full ${syne.className}`}>
<MobileWarning />

<DragLayer />

<CommandPalette
workspaceId={workspaceId}
isOpen={isSearchOpen}
Expand Down

0 comments on commit 082c7ff

Please sign in to comment.