diff --git a/frontend/src/components/TagDetailSidebar.tsx b/frontend/src/components/TagDetailSidebar.tsx index 57ef996..18256c7 100644 --- a/frontend/src/components/TagDetailSidebar.tsx +++ b/frontend/src/components/TagDetailSidebar.tsx @@ -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 { 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 ( +
  • + {over &&
    } +
  • ) +} + + type Props = { post: Post | null } @@ -180,26 +222,58 @@ export default (({ post }: Props) => { useSensor (PointerSensor, { activationConstraint: { distance: 6 } })) 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 parentId: number | undefined = e.over?.data.current?.tagId + const fromParentId: number | undefined = e.active.data.current?.parentTagId - if (!(childId) || !(parentId)) - return + const overKind = e.over?.data.current?.kind - if (childId === parentId) + if (!(childId) || !(overKind)) return - setTags (prev => { - const child = findTag (prev, childId) - const parent = findTag (prev, parentId) - if (!(child) || !(parent)) - return prev + if (overKind === 'tag') + { + const parentId: number | undefined = e.over?.data.current?.tagId + if (!(parentId) || childId === parentId) + return + + setTags (prev => { + const child = findTag (prev, childId) + const parent = findTag (prev, parentId) + if (!(child) || !(parent) || isDescendant (child, parentId)) + return prev + + return attachChildOptimistic (prev, parentId, childId) + }) - if (isDescendant (child, parentId)) - return prev + return + } - return attachChildOptimistic (prev, parentId, childId) - }) + 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 = { @@ -260,7 +334,10 @@ export default (({ post }: Props) => { {tags[cat].map (tag => ( - renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)))} + <> + {renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)} + ))} + ))}