|
|
|
@@ -3,6 +3,7 @@ import { DndContext, |
|
|
|
useDroppable, |
|
|
|
useSensor, |
|
|
|
useSensors } from '@dnd-kit/core' |
|
|
|
import axios from 'axios' |
|
|
|
import { AnimatePresence, motion } from 'framer-motion' |
|
|
|
import { useEffect, useRef, useState } from 'react' |
|
|
|
|
|
|
|
@@ -11,6 +12,7 @@ import TagSearch from '@/components/TagSearch' |
|
|
|
import SectionTitle from '@/components/common/SectionTitle' |
|
|
|
import SubsectionTitle from '@/components/common/SubsectionTitle' |
|
|
|
import SidebarComponent from '@/components/layout/SidebarComponent' |
|
|
|
import { API_BASE_URL } from '@/config' |
|
|
|
import { CATEGORIES } from '@/consts' |
|
|
|
|
|
|
|
import type { DragEndEvent } from '@dnd-kit/core' |
|
|
|
@@ -44,38 +46,12 @@ const renderTagTree = ( |
|
|
|
suppressClickRef={suppressClickRef}/> |
|
|
|
</motion.li>) |
|
|
|
|
|
|
|
return [self, |
|
|
|
...((tag.children |
|
|
|
?.sort ((a, b) => a.name < b.name ? -1 : 1) |
|
|
|
.flatMap (child => ( |
|
|
|
renderTagTree (child, nestLevel + 1, key, suppressClickRef, tag.id)))) |
|
|
|
?? [])] |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const removeEverywhere = ( |
|
|
|
list: Tag[], |
|
|
|
tagId: number, |
|
|
|
): { next: Tag[] |
|
|
|
picked?: Tag } => { |
|
|
|
let picked: Tag | undefined |
|
|
|
|
|
|
|
const walk = (nodes: Tag[]): Tag[] => ( |
|
|
|
nodes |
|
|
|
.map (t => { |
|
|
|
const children = t.children ? walk (t.children) : undefined |
|
|
|
return children ? { ...t, children } : t |
|
|
|
}) |
|
|
|
.filter (t => { |
|
|
|
if (t.id === tagId) |
|
|
|
{ |
|
|
|
picked = picked ?? t |
|
|
|
return false |
|
|
|
} |
|
|
|
return true |
|
|
|
})) |
|
|
|
|
|
|
|
return { next: walk (list), picked } |
|
|
|
return [ |
|
|
|
self, |
|
|
|
...((tag.children |
|
|
|
?.sort ((a, b) => a.name < b.name ? -1 : 1) |
|
|
|
.flatMap (child => renderTagTree (child, nestLevel + 1, key, suppressClickRef, tag.id))) |
|
|
|
?? [])] |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@@ -100,30 +76,6 @@ const addAsChild = ( |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const attachChildOptimistic = ( |
|
|
|
prev: TagByCategory, |
|
|
|
parentId: number, |
|
|
|
childId: number, |
|
|
|
): TagByCategory => { |
|
|
|
const next: TagByCategory = { ...prev } |
|
|
|
|
|
|
|
let picked: Tag | undefined |
|
|
|
for (const cat of Object.keys (next) as (keyof typeof next)[]) |
|
|
|
{ |
|
|
|
const r = removeEverywhere (next[cat], childId) |
|
|
|
next[cat] = r.next |
|
|
|
picked = picked ?? r.picked |
|
|
|
} |
|
|
|
if (!(picked)) |
|
|
|
return prev |
|
|
|
|
|
|
|
for (const cat of Object.keys (next) as (keyof typeof next)[]) |
|
|
|
next[cat] = addAsChild (next[cat], parentId, picked) |
|
|
|
|
|
|
|
return next |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const isDescendant = ( |
|
|
|
root: Tag, |
|
|
|
targetId: number, |
|
|
|
@@ -210,6 +162,12 @@ const DropSlot = ({ cat, index }: { cat: Category, index: number }) => { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const removeFromRoot = ( |
|
|
|
roots: Tag[], |
|
|
|
childId: number, |
|
|
|
): Tag[] => roots.filter (t => t.id !== childId) |
|
|
|
|
|
|
|
|
|
|
|
type Props = { post: Post | null } |
|
|
|
|
|
|
|
|
|
|
|
@@ -234,46 +192,73 @@ export default (({ post }: Props) => { |
|
|
|
if (!(childId) || !(overKind)) |
|
|
|
return |
|
|
|
|
|
|
|
if (overKind === 'tag') |
|
|
|
{ |
|
|
|
const parentId: number | undefined = e.over?.data.current?.tagId |
|
|
|
if (!(parentId) || childId === parentId) |
|
|
|
return |
|
|
|
switch (overKind) |
|
|
|
{ |
|
|
|
case '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 |
|
|
|
|
|
|
|
const cat = child.category |
|
|
|
const next: TagByCategory = { ...prev } |
|
|
|
|
|
|
|
setTags (prev => { |
|
|
|
const child = findTag (prev, childId) |
|
|
|
const parent = findTag (prev, parentId) |
|
|
|
if (!(child) || !(parent) || isDescendant (child, parentId)) |
|
|
|
return prev |
|
|
|
if (fromParentId) |
|
|
|
next[cat] = detachEdge (next[cat], fromParentId, childId) |
|
|
|
else |
|
|
|
next[cat] = removeFromRoot (next[cat], childId) |
|
|
|
|
|
|
|
return attachChildOptimistic (prev, parentId, childId) |
|
|
|
}) |
|
|
|
next[cat] = addAsChild (next[cat], parentId, child) |
|
|
|
|
|
|
|
return next |
|
|
|
}) |
|
|
|
|
|
|
|
if (fromParentId) |
|
|
|
{ |
|
|
|
await axios.delete ( |
|
|
|
`${ API_BASE_URL }/tags/${ fromParentId }/children/${ childId }`, |
|
|
|
{ headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } }) |
|
|
|
} |
|
|
|
|
|
|
|
await axios.post ( |
|
|
|
`${ API_BASE_URL }/tags/${ parentId }/children/${ childId }`, |
|
|
|
{ }, |
|
|
|
{ headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } }) |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
case 'slot': |
|
|
|
const cat: Category | undefined = e.over?.data.current?.cat |
|
|
|
const index: number | undefined = e.over?.data.current?.index |
|
|
|
if (!(cat) || index == null) |
|
|
|
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 |
|
|
|
|
|
|
|
setTags (prev => { |
|
|
|
const child = findTag (prev, childId) |
|
|
|
if (!(child)) |
|
|
|
return prev |
|
|
|
const next: TagByCategory = { ...prev } |
|
|
|
|
|
|
|
const next: TagByCategory = { ...prev } |
|
|
|
if (fromParentId) |
|
|
|
next[cat] = detachEdge (next[cat], fromParentId, childId) as any |
|
|
|
|
|
|
|
if (fromParentId) |
|
|
|
next[cat] = detachEdge (next[cat], fromParentId, childId) as any |
|
|
|
next[cat] = insertRootAt (next[cat], index, child) |
|
|
|
|
|
|
|
next[cat] = insertRootAt (next[cat], index, child) |
|
|
|
return next |
|
|
|
}) |
|
|
|
|
|
|
|
return next |
|
|
|
}) |
|
|
|
} |
|
|
|
await axios.delete ( |
|
|
|
`${ API_BASE_URL }/tags/${ fromParentId }/children/${ childId }`, |
|
|
|
{ headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } }) |
|
|
|
|
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const categoryNames: Record<Category, string> = { |
|
|
|
|