ndyudev / trello-web

1 stars 0 forks source link

Drag Drop Card P6 #42

Closed ndyudev closed 3 weeks ago

ndyudev commented 3 weeks ago

import Box from '@mui/material/Box' import ListColumns from './ListColumns/ListColumns' import { mapOrder } from '~/utils/sorts' import { DndContext, // PointerSensor, useSensor, useSensors, MouseSensor, TouchSensor, DragOverlay, defaultDropAnimationSideEffects } from '@dnd-kit/core' import { arrayMove } from '@dnd-kit/sortable' import { useEffect, useState } from 'react' import { cloneDeep } from 'lodash'

import Column from './ListColumns/Column/Column' import Card from './ListColumns/Column/ListCards/Card/Card'

const ACTIVE_DRAG_ITEM_TYPE = { COLUMN: 'ACTIVE_DRAG_ITEM_TYPE_COLUMN', CARD: 'ACTIVE_DRAG_ITEM_TYPE_CARD' }

function BoardContent({ board }) { // Nếu dùng PointerSensor mặc định phải kết hợp thuộc tính CSS touch-action: none ở các phần tử kéo thả - Nhưng còn bug // const pointerSensor = useSensor(PointerSensor, { activationConstraint:{ distance: 10}}) // Yêu cầu chuột di chuyển 10px thì mới kích hoạt event, fix trường hợp click bị gọi event const mouseSensor = useSensor(MouseSensor, { activationConstraint:{ distance: 10}}) // Nhấn giữ 250ms và dung sai của cảm ứng (di chuyển chênh lệch 500px) thì mới kích hoạt event const toucherSensor = useSensor(TouchSensor, { activationConstraint:{ delay: 250, tolerance: 500}}) // Ưu tiên sử dụng kết hợp 2 loại sensors là mouse và touch để có trải nghiệm trên mobile tốt nhất không bị bug // const sensors = useSensors(pointerSensor) const sensors = useSensors(mouseSensor, toucherSensor)

const [orderedColumns, setOrderedColumns] = useState([])

// Cùng một thời điểm chỉ có một phần tử được kéo (column hoặc card) const [activeDragItemId, setActiveDragItemID] = useState(null) const [activeDragItemType, setActiveDragItemType] = useState(null) const [activeDragItemData, setActiveDragItemData] = useState(null) const [oldColumnWhenDraggingCard, setOldColumnWhenDraggingCard] = useState(null)

useEffect(() => { setOrderedColumns(mapOrder(board?.columns, board?.columnOrderIds, '_id')) }, [board])

// Tìm một column theo cardId const findColumnByCardId = (cardId) => { // Dùng c.cards thay vì c.cardOrderIds vì trong handleDragOver dữ liệu cards sẽ hoàn chỉnh trước khi tạo CardOrderIds mới return orderedColumns.find(column => column?.cards?.map(card => card._id)?.includes(cardId)) }

const handleDragStart = (event) => { setActiveDragItemID(event?.active?.id) setActiveDragItemType(event?.active?.data?.current?.columnId ? ACTIVE_DRAG_ITEM_TYPE.CARD : ACTIVE_DRAG_ITEM_TYPE.COLUMN) setActiveDragItemData(event?.active?.data?.current) // console.log('handleDragStart:', event)

// Nếu là kéo card thì mới thực hiện hành động như set giá trị oldColumn
if(event?.active?.data?.current?.columnId) {
  setOldColumnWhenDraggingCard(findColumnByCardId(event?.active?.id))
}

}

// Trigger trong quá trình kéo const handleDragOver = (event) => { // Không làm gì nếu đang kéo column if (activeDragItemType === ACTIVE_DRAG_ITEM_TYPE.COLUMN) return // console.log('handleDragOver:', event)

const { active, over } = event
if (!active || !over) return

const { id: activeDraggingCardId, data: { current: activeDraggingCardData }} = active
const { id: OverCardId } = over

const activeColumn = findColumnByCardId(activeDraggingCardId)
const overColumn = findColumnByCardId(OverCardId)

// Nếu không tồn tại 1 trong 2 column thì không làm gì để tránh crash trang web
if (!activeColumn || !overColumn) return

// Xử lý logic khi kéo card giữa các columns khác nhau
if (activeColumn._id !== overColumn._id) {
  setOrderedColumns(prevColumns => {
    const overCardIndex = overColumn?.cards?.findIndex(card => card._id === OverCardId)
    let newCardIndex
    const isBelowOverItem = active.rect.current.translated && active.rect.current.translated.top > over.rect.top + over.rect.height
    const modifier = isBelowOverItem ? 1 : 0
    newCardIndex = overCardIndex >= 0 ? overCardIndex + modifier : overColumn?.cards?.length + 1

    // Clone mảng OrderredColumnsState cũ ra một cái mới để xử lý data rồi return - cập nhập lại OrderedColumnsState mới
    const nextColumns = cloneDeep(prevColumns)
    const nextActiveColumn = nextColumns.find(column => column._id === activeColumn._id)
    const nextOverColumn = nextColumns.find(column => column._id === overColumn._id)

    if (nextActiveColumn) {
      nextActiveColumn.cards = nextActiveColumn.cards.filter(card => card._id !== activeDraggingCardId)
      nextActiveColumn.cardOrderIds = nextActiveColumn.cards.map(card => card._id)
    }

    if (nextOverColumn) {
      nextOverColumn.cards = nextOverColumn.cards.filter(card => card._id !== activeDraggingCardId)
      nextOverColumn.cards = nextOverColumn.cards.toSpliced(newCardIndex, 0, activeDraggingCardData)
      nextOverColumn.cardOrderIds = nextOverColumn.cards.map(card => card._id)
    }

    return nextColumns
  })
}

}

const handleDragEnd = (event) => { const { active, over } = event if (!active || !over) return

if (activeDragItemType === ACTIVE_DRAG_ITEM_TYPE.CARD) {
  const { id: activeDraggingCardId, data: { current: activeDraggingCardData }} = active
  const { id: OverCardId } = over

  const activeColumn = findColumnByCardId(activeDraggingCardId)
  const overColumn = findColumnByCardId(OverCardId)

  // Nếu không tồn tại 1 trong 2 column thì không làm gì để tránh crash trang web
  if (!activeColumn || !overColumn) return
  //Keos card qua 2 column khasc nhau
  // Phải dung tới activeDragItemData ( set vào state từ bước handleDragStart ) chứ không phải acticeData
  // tong scope handleDragEnd này vì sau khi đi qua onDragOver tới đây là stats của card đã bị cập nhập một lần rồi.
  if (oldColumnWhenDraggingCard._id !== overColumn._id) {
    //
  } else {
    // hành động kéo thả card trong cùng 1 column

    // Lấy vị trí cũ ( từ thằng  oldColumnWhenDraggingCard)
    const oldCardIndex = oldColumnWhenDraggingCard?.cards?.findIndex(c => c._id === activeDragItemId)
    // Lấy vị trí mới ( từ thằng  )
    const newCardIndex = overColumn?.cards?.findIndex(c => c._id === OverCardId)
    // Dùng arrayMove vì kéo card trong một cái column thì tương tự với logic 
    const dndOrderedCards = arrayMove(oldColumnWhenDraggingCard?.cards, oldCardIndex, newCardIndex)

    setOrderedColumns(prevColumns => {
      // Clone mảng OrderredColumnsState cũ ra một cái mới để xử lý data rồi return - cập nhập lại OrderedColumnsState mới
      const nextColumns = cloneDeep(prevColumns)

      // Tìm tới column mà chúng ta đang thả.
    })
  }
}
// Xử lý kleos thả Columns trong cùng 1 một cái boardContent
if (activeDragItemType === ACTIVE_DRAG_ITEM_TYPE.COLUMN) {
  if (active.id !== over.id) {
    // Lấy vị trí cũ ( từ thằng active )
    const oldColumnIndex = orderedColumns.findIndex(c => c._id === active.id)
    // Lấy vị trí mới ( từ thằng over )
    const newColumnIndex = orderedColumns.findIndex(c => c._id === over.id)
    // Dùng arrayMove của thằng dnd-kit để sắp xếp lại mảng Columns ban đầu
    // Code của arrayMove ở đây : Dnd - kit / ArrayMove.ts
    const dndOrderedColumns = arrayMove(orderedColumns, oldColumnIndex, newColumnIndex)

    setOrderedColumns(dndOrderedColumns)
  }
}
// Những dữ liệu sau khi kéo thả này luôn phải đưa về giá trị null mặc định ban đầu
setActiveDragItemID(null)
setActiveDragItemData(null)
setActiveDragItemType(null)
setOldColumnWhenDraggingCard(null)

}

const customDropAnimation = { sideEffects: defaultDropAnimationSideEffects({ styles: { active: { opacity: '0.5' } } }) }

return ( <DndContext onDragStart={handleDragStart} onDragOver={handleDragOver} onDragEnd={handleDragEnd} sensors={sensors}

<Box sx={{ backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#34495e' : '#1976d2', width: '100%', height: (theme) => theme.trello.BoardContentHeight, p: '10px 0', }}

{!activeDragItemType && null } {activeDragItemType === ACTIVE_DRAG_ITEM_TYPE.COLUMN && } {activeDragItemType === ACTIVE_DRAG_ITEM_TYPE.CARD && }

) }

export default BoardContent