This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
|
import { DndContext,
|
||||||
|
PointerSensor,
|
||||||
|
useDroppable,
|
||||||
|
useSensor,
|
||||||
|
useSensors } from '@dnd-kit/core'
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
@@ -168,6 +172,44 @@ const findTag = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const detachEdge = (
|
||||||
|
nodes: Tag[],
|
||||||
|
parentId: number,
|
||||||
|
childId: number,
|
||||||
|
): Tag[] => nodes.map (t => {
|
||||||
|
if (t.id === parentId)
|
||||||
|
{
|
||||||
|
const children = (t.children ?? []).filter (c => c.id !== childId)
|
||||||
|
return { ...t, children }
|
||||||
|
}
|
||||||
|
return t.children ? { ...t, children: detachEdge (t.children, parentId, childId) } : t
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const insertRootAt = (
|
||||||
|
roots: Tag[],
|
||||||
|
index: number,
|
||||||
|
tag: Tag,
|
||||||
|
): Tag[] => {
|
||||||
|
const without = roots.filter (t => t.id !== tag.id)
|
||||||
|
const next = without.slice ()
|
||||||
|
next.splice (Math.min (Math.max (index, 0), next.length), 0, tag)
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DropSlot = ({ cat, index }: { cat: Category, index: number }) => {
|
||||||
|
const { setNodeRef, isOver: over } = useDroppable ({
|
||||||
|
id: `slot:${ cat }:${ index }`,
|
||||||
|
data: { kind: 'slot', cat, index } })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li ref={setNodeRef} className="h-1">
|
||||||
|
{over && <div className="h-0.5 w-full rounded bg-sky-400"/>}
|
||||||
|
</li>)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Props = { post: Post | null }
|
type Props = { post: Post | null }
|
||||||
|
|
||||||
|
|
||||||
@@ -180,26 +222,58 @@ export default (({ post }: Props) => {
|
|||||||
useSensor (PointerSensor, { activationConstraint: { distance: 6 } }))
|
useSensor (PointerSensor, { activationConstraint: { distance: 6 } }))
|
||||||
|
|
||||||
const onDragEnd = async (e: DragEndEvent) => {
|
const onDragEnd = async (e: DragEndEvent) => {
|
||||||
|
const activeKind = e.active.data.current?.kind
|
||||||
|
if (activeKind !== 'tag')
|
||||||
|
return
|
||||||
|
|
||||||
const childId: number | undefined = e.active.data.current?.tagId
|
const childId: number | undefined = e.active.data.current?.tagId
|
||||||
const parentId: number | undefined = e.over?.data.current?.tagId
|
const fromParentId: number | undefined = e.active.data.current?.parentTagId
|
||||||
|
|
||||||
if (!(childId) || !(parentId))
|
const overKind = e.over?.data.current?.kind
|
||||||
|
|
||||||
|
if (!(childId) || !(overKind))
|
||||||
return
|
return
|
||||||
|
|
||||||
if (childId === parentId)
|
if (overKind === 'tag')
|
||||||
return
|
{
|
||||||
|
const parentId: number | undefined = e.over?.data.current?.tagId
|
||||||
|
if (!(parentId) || childId === parentId)
|
||||||
|
return
|
||||||
|
|
||||||
setTags (prev => {
|
setTags (prev => {
|
||||||
const child = findTag (prev, childId)
|
const child = findTag (prev, childId)
|
||||||
const parent = findTag (prev, parentId)
|
const parent = findTag (prev, parentId)
|
||||||
if (!(child) || !(parent))
|
if (!(child) || !(parent) || isDescendant (child, parentId))
|
||||||
return prev
|
return prev
|
||||||
|
|
||||||
if (isDescendant (child, parentId))
|
return attachChildOptimistic (prev, parentId, childId)
|
||||||
return prev
|
})
|
||||||
|
|
||||||
return attachChildOptimistic (prev, parentId, childId)
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (overKind === 'slot')
|
||||||
|
{
|
||||||
|
const cat: Category | undefined = e.over?.data.current?.cat
|
||||||
|
const index: number | undefined = e.over?.data.current?.index
|
||||||
|
if (!(cat) || index == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
setTags (prev => {
|
||||||
|
const child = findTag (prev, childId)
|
||||||
|
if (!(child))
|
||||||
|
return prev
|
||||||
|
|
||||||
|
const next: TagByCategory = { ...prev }
|
||||||
|
|
||||||
|
if (fromParentId)
|
||||||
|
next[cat] = detachEdge (next[cat], fromParentId, childId) as any
|
||||||
|
|
||||||
|
next[cat] = insertRootAt (next[cat], index, child)
|
||||||
|
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryNames: Record<Category, string> = {
|
const categoryNames: Record<Category, string> = {
|
||||||
@@ -260,7 +334,10 @@ export default (({ post }: Props) => {
|
|||||||
<motion.ul layout>
|
<motion.ul layout>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{tags[cat].map (tag => (
|
{tags[cat].map (tag => (
|
||||||
renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)))}
|
<>
|
||||||
|
{renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)}
|
||||||
|
</>))}
|
||||||
|
<DropSlot cat={cat} index={0}/>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</motion.ul>
|
</motion.ul>
|
||||||
</motion.div>))}
|
</motion.div>))}
|
||||||
|
|||||||
Reference in New Issue
Block a user