このコミットが含まれているのは:
@@ -58,15 +58,14 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
|
||||
data: { kind: 'tag',
|
||||
tagId: tag.id,
|
||||
parentTagId,
|
||||
nestLevel,
|
||||
dndId } })
|
||||
nestLevel } })
|
||||
|
||||
const { setNodeRef: setDropRef, isOver: over } = useDroppable ({
|
||||
id: dndId,
|
||||
data: { kind: 'tag', tagId: tag.id } })
|
||||
|
||||
const style: CSSProperties = { transform: CSS.Translate.toString (transform),
|
||||
visibility: dragging ? 'hidden' : 'visible' }
|
||||
visibility: dragging && !(sp) ? 'hidden' : 'visible' }
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -79,12 +78,16 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
|
||||
return
|
||||
const dx = e.clientX - p.x
|
||||
const dy = e.clientY - p.y
|
||||
|
||||
if (dx * dx + dy * dy >= 9)
|
||||
armEatNextClick ()
|
||||
}}
|
||||
onPointerUpCapture={() => {
|
||||
downPosRef.current = null
|
||||
}}
|
||||
onPointerCancelCapture={() => {
|
||||
downPosRef.current = null
|
||||
}}
|
||||
onClickCapture={e => {
|
||||
if (suppressClickRef.current)
|
||||
{
|
||||
@@ -100,7 +103,6 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
|
||||
>
|
||||
<motion.div
|
||||
ref={setDragRef}
|
||||
data-tag-dnd-id={dndId}
|
||||
style={style}
|
||||
className={cn (
|
||||
'inline-flex min-w-0 max-w-full items-baseline overflow-hidden',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DndContext,
|
||||
DragOverlay,
|
||||
MeasuringStrategy,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
type Modifier,
|
||||
pointerWithin,
|
||||
rectIntersection,
|
||||
useDroppable,
|
||||
@@ -11,7 +11,7 @@ import { DndContext,
|
||||
import { restrictToWindowEdges } from '@dnd-kit/modifiers'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { motion } from 'framer-motion'
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import DraggableDroppableTagRow from '@/components/DraggableDroppableTagRow'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
@@ -28,13 +28,21 @@ import { postsKeys, tagsKeys } from '@/lib/queryKeys'
|
||||
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
||||
import { dateString, originalCreatedAtString } from '@/lib/utils'
|
||||
|
||||
import type { DragEndEvent } from '@dnd-kit/core'
|
||||
import type { CollisionDetection, DragEndEvent } from '@dnd-kit/core'
|
||||
import type { FC, MutableRefObject, ReactNode } from 'react'
|
||||
|
||||
import type { Category, Post, TagWithSections } from '@/types'
|
||||
|
||||
type TagByCategory = { [key in Category]: TagWithSections[] }
|
||||
|
||||
const tagCollisionDetection: CollisionDetection = args => {
|
||||
const byPointer = pointerWithin (args)
|
||||
return byPointer.length > 0 ? byPointer : rectIntersection (args)
|
||||
}
|
||||
|
||||
const alwaysMeasureDroppables = {
|
||||
droppable: { strategy: MeasuringStrategy.Always } } as const
|
||||
|
||||
|
||||
const renderTagTree = (
|
||||
tag: TagWithSections,
|
||||
@@ -180,6 +188,24 @@ const DropSlot = ({ cat }: { cat: Category }) => {
|
||||
</li>)
|
||||
}
|
||||
|
||||
const EmptyCategoryDropSection = (
|
||||
{ cat, children }: { cat: Category
|
||||
children: ReactNode }) => {
|
||||
const { setNodeRef, isOver: over } = useDroppable ({
|
||||
id: `slot:${ cat }`,
|
||||
data: { kind: 'slot', cat } })
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} className="my-3">
|
||||
{children}
|
||||
<ul>
|
||||
<li className="h-1">
|
||||
{over && <div className="h-0.5 w-full rounded bg-sky-400"/>}
|
||||
</li>
|
||||
</ul>
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -188,12 +214,7 @@ type Props = {
|
||||
|
||||
type ActiveTagDrag = {
|
||||
tagId: number
|
||||
nestLevel: number
|
||||
dndId: string }
|
||||
|
||||
type DragOverlayOffset = {
|
||||
x: number
|
||||
y: number }
|
||||
nestLevel: number }
|
||||
|
||||
|
||||
const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||
@@ -228,59 +249,16 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||
const [dragging, setDragging] = useState (false)
|
||||
const [saving, setSaving] = useState (false)
|
||||
const [tags, setTags] = useState (baseTags)
|
||||
const [dragOverlayOffset, setDragOverlayOffset] =
|
||||
useState<DragOverlayOffset> ({ x: 0, y: 0 })
|
||||
const flatTagsByCategory = useMemo<TagByCategory> (
|
||||
() => buildFlatTagByCategory (tags),
|
||||
[tags],
|
||||
)
|
||||
|
||||
const suppressClickRef = useRef (false)
|
||||
const dragStartRectRef = useRef<DOMRect | null> (null)
|
||||
const offsetFrameRef = useRef<number | null> (null)
|
||||
|
||||
const sensors = useSensors (
|
||||
useSensor (MouseSensor, { activationConstraint: { distance: 6 } }),
|
||||
useSensor (TouchSensor, { activationConstraint: { delay: 250, tolerance: 8 } }))
|
||||
const dragOverlayModifiers = useMemo<Modifier[]> (
|
||||
() => (
|
||||
sp
|
||||
? [({ transform }) => ({
|
||||
...transform,
|
||||
x: transform.x + dragOverlayOffset.x,
|
||||
y: transform.y + dragOverlayOffset.y })]
|
||||
: []),
|
||||
[dragOverlayOffset.x, dragOverlayOffset.y, sp],
|
||||
)
|
||||
|
||||
useLayoutEffect (() => {
|
||||
if (!(sp && dragging && activeTagDrag && dragStartRectRef.current))
|
||||
{
|
||||
setDragOverlayOffset ({ x: 0, y: 0 })
|
||||
return
|
||||
}
|
||||
|
||||
offsetFrameRef.current = requestAnimationFrame (() => {
|
||||
const node = document.querySelector<HTMLElement> (
|
||||
`[data-tag-dnd-id="${ activeTagDrag.dndId }"]`,
|
||||
)
|
||||
|
||||
if (!(node && dragStartRectRef.current))
|
||||
return
|
||||
|
||||
const currentRect = node.getBoundingClientRect ()
|
||||
const initialRect = dragStartRectRef.current
|
||||
|
||||
setDragOverlayOffset ({
|
||||
x: currentRect.left - initialRect.left,
|
||||
y: currentRect.top - initialRect.top })
|
||||
})
|
||||
|
||||
return () => {
|
||||
if (offsetFrameRef.current != null)
|
||||
cancelAnimationFrame (offsetFrameRef.current)
|
||||
}
|
||||
}, [activeTagDrag, dragging, sp])
|
||||
|
||||
const reloadTags = async (): Promise<void> => {
|
||||
setTags (buildTagByCategory (await apiGet<Post> (`/posts/${ post.id }`)))
|
||||
@@ -383,28 +361,17 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||
<TagSearch/>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={args => {
|
||||
const byPointer = pointerWithin (args)
|
||||
return byPointer.length > 0 ? byPointer : rectIntersection (args)
|
||||
}}
|
||||
collisionDetection={sp ? tagCollisionDetection : undefined}
|
||||
measuring={sp ? alwaysMeasureDroppables : undefined}
|
||||
onDragStart={e => {
|
||||
if (e.active.data.current?.kind === 'tag')
|
||||
{
|
||||
const tagId = e.active.data.current?.tagId
|
||||
const nestLevel = e.active.data.current?.nestLevel
|
||||
const dndId = e.active.data.current?.dndId
|
||||
dragStartRectRef.current =
|
||||
e.active.rect.current.initial
|
||||
? new DOMRect (
|
||||
e.active.rect.current.initial.left,
|
||||
e.active.rect.current.initial.top,
|
||||
e.active.rect.current.initial.width,
|
||||
e.active.rect.current.initial.height)
|
||||
: null
|
||||
setActiveTagDrag (
|
||||
tagId == null || nestLevel == null || !(dndId)
|
||||
tagId == null || nestLevel == null
|
||||
? null
|
||||
: { tagId, nestLevel, dndId })
|
||||
: { tagId, nestLevel })
|
||||
}
|
||||
setDragging (true)
|
||||
suppressClickRef.current = true
|
||||
@@ -418,16 +385,12 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||
onDragCancel={() => {
|
||||
setActiveTagDrag (null)
|
||||
setDragging (false)
|
||||
setDragOverlayOffset ({ x: 0, y: 0 })
|
||||
dragStartRectRef.current = null
|
||||
document.body.style.userSelect = ''
|
||||
suppressClickRef.current = false
|
||||
}}
|
||||
onDragEnd={async e => {
|
||||
setActiveTagDrag (null)
|
||||
setDragging (false)
|
||||
setDragOverlayOffset ({ x: 0, y: 0 })
|
||||
dragStartRectRef.current = null
|
||||
await onDragEnd (e)
|
||||
document.body.style.userSelect = ''
|
||||
}}
|
||||
@@ -441,18 +404,26 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||
if (!(categoryTags.length > 0 || dragging))
|
||||
return null
|
||||
|
||||
const sectionTitle = (
|
||||
<SubsectionTitle>
|
||||
<motion.div
|
||||
layoutId={animationMode === 'off'
|
||||
? undefined
|
||||
: `tag-${ sp ? 'sp-' : '' }${ cat }`}
|
||||
transition={{ layout: layoutTransition }}>
|
||||
{CATEGORY_NAMES[cat]}
|
||||
</motion.div>
|
||||
</SubsectionTitle>)
|
||||
|
||||
if (!(categoryTags.length > 0))
|
||||
return (
|
||||
<EmptyCategoryDropSection cat={cat} key={cat}>
|
||||
{sectionTitle}
|
||||
</EmptyCategoryDropSection>)
|
||||
|
||||
return (
|
||||
<div className="my-3" key={cat}>
|
||||
<SubsectionTitle>
|
||||
<motion.div
|
||||
layoutId={animationMode === 'off'
|
||||
? undefined
|
||||
: `tag-${ sp ? 'sp-' : '' }${ cat }`}
|
||||
transition={{ layout: layoutTransition }}>
|
||||
{CATEGORY_NAMES[cat]}
|
||||
</motion.div>
|
||||
</SubsectionTitle>
|
||||
|
||||
{sectionTitle}
|
||||
<ul>
|
||||
{(tagRelationDisplay === 'grouped'
|
||||
? (tags[cat] ?? []).flatMap (tag =>
|
||||
@@ -520,16 +491,15 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||
</ul>
|
||||
</motion.div>)}
|
||||
|
||||
<DragOverlay adjustScale={false} modifiers={dragOverlayModifiers}>
|
||||
<div className="pointer-events-none">
|
||||
{activeTagDrag != null && (() => {
|
||||
const tag = findTag (tags, activeTagDrag.tagId)
|
||||
return (
|
||||
tag
|
||||
&& <TagLink tag={tag} nestLevel={activeTagDrag.nestLevel}/>)
|
||||
}) ()}
|
||||
</div>
|
||||
</DragOverlay>
|
||||
{!(sp) && (
|
||||
<DragOverlay adjustScale={false}>
|
||||
<div className="pointer-events-none">
|
||||
{activeTagDrag != null && (() => {
|
||||
const tag = findTag (tags, activeTagDrag.tagId)
|
||||
return tag && <TagLink tag={tag} nestLevel={activeTagDrag.nestLevel}/>
|
||||
}) ()}
|
||||
</div>
|
||||
</DragOverlay>)}
|
||||
</DndContext>
|
||||
</SidebarComponent>)
|
||||
}
|
||||
|
||||
新しい課題から参照
ユーザをブロックする