feat: support dnd to reorder favorite relays

This commit is contained in:
codytseng
2025-08-17 18:33:52 +08:00
parent 9bdee807ee
commit 4e40662f22
4 changed files with 90 additions and 8 deletions

View File

@@ -1,17 +1,63 @@
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import {
closestCenter,
DndContext,
DragEndEvent,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from '@dnd-kit/core'
import { restrictToParentElement, restrictToVerticalAxis } from '@dnd-kit/modifiers'
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy
} from '@dnd-kit/sortable'
import { useTranslation } from 'react-i18next'
import RelayItem from './RelayItem'
export default function FavoriteRelayList() {
const { t } = useTranslation()
const { favoriteRelays } = useFavoriteRelays()
const { favoriteRelays, reorderFavoriteRelays } = useFavoriteRelays()
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
)
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
if (over && active.id !== over.id) {
const oldIndex = favoriteRelays.findIndex((relay) => relay === active.id)
const newIndex = favoriteRelays.findIndex((relay) => relay === over.id)
const reorderedRelays = arrayMove(favoriteRelays, oldIndex, newIndex)
reorderFavoriteRelays(reorderedRelays)
}
}
return (
<div className="space-y-2">
<div className="text-muted-foreground font-semibold select-none">{t('Relays')}</div>
{favoriteRelays.map((relay) => (
<RelayItem key={relay} relay={relay} />
))}
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
>
<SortableContext items={favoriteRelays} strategy={verticalListSortingStrategy}>
<div className="grid gap-2">
{favoriteRelays.map((relay) => (
<RelayItem key={relay} relay={relay} />
))}
</div>
</SortableContext>
</DndContext>
</div>
)
}

View File

@@ -1,18 +1,43 @@
import { toRelay } from '@/lib/link'
import { useSecondaryPage } from '@/PageManager'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { GripVertical } from 'lucide-react'
import RelayIcon from '../RelayIcon'
import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu'
export default function RelayItem({ relay }: { relay: string }) {
const { push } = useSecondaryPage()
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: relay
})
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1
}
return (
<div
className="flex gap-2 border rounded-lg p-4 items-center clickable select-none"
className="relative group clickable flex gap-2 border rounded-lg p-2 pr-2.5 items-center justify-between select-none"
ref={setNodeRef}
style={style}
onClick={() => push(toRelay(relay))}
>
<RelayIcon url={relay} />
<div className="flex-1 w-0 truncate font-semibold">{relay}</div>
<div className="flex items-center gap-1 flex-1">
<div
className="cursor-grab active:cursor-grabbing p-2 hover:bg-muted rounded touch-none shrink-0"
{...attributes}
{...listeners}
>
<GripVertical className="size-4 text-muted-foreground" />
</div>
<div className="flex gap-2 items-center flex-1">
<RelayIcon url={relay} />
<div className="flex-1 w-0 truncate font-semibold">{relay}</div>
</div>
</div>
<SaveRelayDropdownMenu urls={[relay]} />
</div>
)

View File

@@ -88,7 +88,9 @@ export default function SaveRelayDropdownMenu({
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
<DropdownMenuTrigger asChild className="px-2">
{trigger}
</DropdownMenuTrigger>
<DropdownMenuContent onClick={(e) => e.stopPropagation()}>
<DropdownMenuLabel>{t('Save to')} ...</DropdownMenuLabel>
<DropdownMenuSeparator />