This commit is contained in:
@@ -12,6 +12,7 @@ import TagSearch from '@/components/TagSearch'
|
|||||||
import SectionTitle from '@/components/common/SectionTitle'
|
import SectionTitle from '@/components/common/SectionTitle'
|
||||||
import SubsectionTitle from '@/components/common/SubsectionTitle'
|
import SubsectionTitle from '@/components/common/SubsectionTitle'
|
||||||
import SidebarComponent from '@/components/layout/SidebarComponent'
|
import SidebarComponent from '@/components/layout/SidebarComponent'
|
||||||
|
import { toast } from '@/components/ui/use-toast'
|
||||||
import { API_BASE_URL } from '@/config'
|
import { API_BASE_URL } from '@/config'
|
||||||
import { CATEGORIES } from '@/consts'
|
import { CATEGORIES } from '@/consts'
|
||||||
|
|
||||||
@@ -55,6 +56,31 @@ const renderTagTree = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const addAsChild = (
|
const addAsChild = (
|
||||||
list: Tag[],
|
list: Tag[],
|
||||||
parentId: number,
|
parentId: number,
|
||||||
@@ -76,6 +102,34 @@ 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)
|
||||||
|
.sort ((a: Tag, b: Tag) => a.name < b.name ? -1 : 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const isDescendant = (
|
const isDescendant = (
|
||||||
root: Tag,
|
root: Tag,
|
||||||
targetId: number,
|
targetId: number,
|
||||||
@@ -150,10 +204,10 @@ const insertRootAt = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const DropSlot = ({ cat, index }: { cat: Category, index: number }) => {
|
const DropSlot = ({ cat }: { cat: Category }) => {
|
||||||
const { setNodeRef, isOver: over } = useDroppable ({
|
const { setNodeRef, isOver: over } = useDroppable ({
|
||||||
id: `slot:${ cat }:${ index }`,
|
id: `slot:${ cat }`,
|
||||||
data: { kind: 'slot', cat, index } })
|
data: { kind: 'slot', cat } })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li ref={setNodeRef} className="h-1">
|
<li ref={setNodeRef} className="h-1">
|
||||||
@@ -162,12 +216,6 @@ 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 }
|
type Props = { post: Post | null }
|
||||||
|
|
||||||
|
|
||||||
@@ -189,55 +237,71 @@ export default (({ post }: Props) => {
|
|||||||
|
|
||||||
const overKind = e.over?.data.current?.kind
|
const overKind = e.over?.data.current?.kind
|
||||||
|
|
||||||
if (!(childId) || !(overKind))
|
if (childId == null || !(overKind))
|
||||||
return
|
return
|
||||||
|
|
||||||
switch (overKind)
|
switch (overKind)
|
||||||
{
|
{
|
||||||
case 'tag':
|
case 'tag':
|
||||||
const parentId: number | undefined = e.over?.data.current?.tagId
|
const parentId: number | undefined = e.over?.data.current?.tagId
|
||||||
if (!(parentId) || childId === parentId)
|
if (parentId == null || childId === parentId)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (fromParentId != null)
|
||||||
|
{
|
||||||
|
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') ?? '' } })
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
toast ({ description: '管理者権限が必要です.' })
|
||||||
|
|
||||||
|
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) || isDescendant (child, parentId))
|
if (!(child) || !(parent) || isDescendant (child, parentId))
|
||||||
return prev
|
return prev
|
||||||
|
|
||||||
const cat = child.category
|
toast ({ description: `《${ child.name }》を《${ parent.name }》の子タグに設定しました.` })
|
||||||
const next: TagByCategory = { ...prev }
|
|
||||||
|
|
||||||
if (fromParentId)
|
return attachChildOptimistic (prev, parentId, childId)
|
||||||
next[cat] = detachEdge (next[cat], fromParentId, childId)
|
|
||||||
else
|
|
||||||
next[cat] = removeFromRoot (next[cat], 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
|
break
|
||||||
|
|
||||||
case 'slot':
|
case 'slot':
|
||||||
const cat: Category | undefined = e.over?.data.current?.cat
|
const cat: Category | undefined = e.over?.data.current?.cat
|
||||||
const index: number | undefined = e.over?.data.current?.index
|
|
||||||
if (!(cat) || index == null)
|
if (fromParentId == null
|
||||||
|
|| !(cat)
|
||||||
|
|| cat !== findTag (tags, childId)?.category)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await axios.delete (
|
||||||
|
`${ API_BASE_URL }/tags/${ fromParentId }/children/${ childId }`,
|
||||||
|
{ headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } })
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
toast ({ description: '管理者権限が必要です.' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setTags (prev => {
|
setTags (prev => {
|
||||||
const child = findTag (prev, childId)
|
const child = findTag (prev, childId)
|
||||||
if (!(child))
|
if (!(child))
|
||||||
@@ -245,18 +309,16 @@ export default (({ post }: Props) => {
|
|||||||
|
|
||||||
const next: TagByCategory = { ...prev }
|
const next: TagByCategory = { ...prev }
|
||||||
|
|
||||||
if (fromParentId)
|
if (fromParentId != null)
|
||||||
next[cat] = detachEdge (next[cat], fromParentId, childId) as any
|
next[cat] = detachEdge (next[cat], fromParentId, childId) as any
|
||||||
|
|
||||||
next[cat] = insertRootAt (next[cat], index, child)
|
next[cat] = insertRootAt (next[cat], 0, child)
|
||||||
|
|
||||||
|
next[cat].sort ((a: Tag, b: Tag) => a.name < b.name ? -1 : 1)
|
||||||
|
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
|
|
||||||
await axios.delete (
|
|
||||||
`${ API_BASE_URL }/tags/${ fromParentId }/children/${ childId }`,
|
|
||||||
{ headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } })
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,7 +384,7 @@ export default (({ post }: Props) => {
|
|||||||
<>
|
<>
|
||||||
{renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)}
|
{renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)}
|
||||||
</>))}
|
</>))}
|
||||||
<DropSlot cat={cat} index={0}/>
|
<DropSlot cat={cat}/>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</motion.ul>
|
</motion.ul>
|
||||||
</motion.div>))}
|
</motion.div>))}
|
||||||
|
|||||||
Reference in New Issue
Block a user